美文网首页Effective C++
【Effective C++(3)】资源管理

【Effective C++(3)】资源管理

作者: downdemo | 来源:发表于2017-12-29 10:39 被阅读9次

    13 以对象管理资源

    • 资源就是一旦使用,将来必须还给系统,C++最常使用的资源是动态分配内存,除此之外常见的资源还包括文件描述器、互斥锁、图形界面的字型和笔刷、数据库连接以及网络sockets
    • 基于对象管理资源建立在C++对构造函数、析构函数、copying函数的基础上
    // 假设A是root class,B是派生类
    B* createB(); // 条款7中提到的工厂函数,调用后需要删除
    void f()
    {
        B* pB = createB();
        ...
        delete pB;
    }
    
    • 这样看起来没问题,但假如“...”中有一个return或者抛出一个异常,或delete位于一个由于某个continue过早退出的循环中,这样delete就会被略过,泄漏的不只是内含对象B的内存,还包括对象B保存的任何资源
    • 为了确保createB返回的对象总被释放,借用C++的析构函数自动调用机制,将资源放进对象中,当控制流离开f,该对象的析构函数会自动释放资源
    • 许多资源被动态分配于heap内而后被用于单一区块或函数内,它们应在控制流离开那个区块或函数时被释放,标准库提供的智能指针就是为此而设计的,其析构函数自动对其所指对象调用delete
    #include <memory>
    void f()
    {
        std::shared_ptr<B> pB(createB());
        ...
    }
    
    • 以对象管理资源的关键想法是,使用RAII对象,获得资源后立即放进管理对象,管理对象运用析构函数确保资源被释放
    • shared_ptr在析构函数内做delete而不是delete[],所以不能给动态分配的array用智能指针,并没有特别针对C++动态分配数组而设计的类似shared_ptr的东西,因为vector和string几乎总是可以取代动态分配的数组,如果希望针对数组设计智能指针,Boost中有boost::shared_array

    14 在资源管理类中小心copying行为

    • 并非所有资源都是heap-based,对这些资源智能指针往往不适合resource handlers,因此偶尔需要建立自己的资源管理类,例如使用C API函数处理Mutex类型的互斥器对象,有lock和unlock两函数可用
    void lock(Mutex* pm); // 锁定pm所指的互斥器
    void unlock(Mutex* pm);
    
    • 为确保不会忘记将一个被锁住的Mutex解锁,建立一个基本结构遵循RAII守则的class来管理
    class Lock{
    public:
        explicit Lock(Mutex* mu) : mutexPtr(mu)
        {
            lock(mutexPtr);
        }
        ~Lock()
        {
            unlock(mutexPtr);
        }
    private:
        Mutex* mutexPtr;
    };
    
    // 客户对Lock的用法符合RAII
    Mutex m; // 定义互斥器
    ...
    { // 建立一个区块用来定义critical section
        Lock m1(&m); // 锁定互斥器
        ... //执行critical section 内的操作
    } // 在区块末尾,自动解除互斥器的锁
    
    // 这很好,但如果RAII对象被复制是不合理的
    Lock m2(&m); // 锁定m
    Lock m3(m2); // 将m2复制到m3上
    
    • 如果RAII对象复制,意味着同一资源被多个管理类管理,资源管理应该有独占性,因此有以下选择
      • 禁止复制,参考条款6(阻止拷贝的方式)
      • 对底层资源使用引用计数,如将Mutex*改为shared_ptr<Mutex>,不过shared_ptr是在引用计数为0时删除所指物,对于Mutex我们想做的动作是锁定而不是删除,可以自己定义删除器,删除器在引用计数为0时被调用
      • 复制底部资源,进行深拷贝。若某类中含有堆或其他资源,该类对象复制时,资源重新分配的过程就是深拷贝,反之则为浅拷贝,浅拷贝前后两个指针指向同一对象,浅拷贝资释放时会因为资源归属不清导致出错
      • 转移底层资源的拥有权,即利用unique_ptr

    15 在资源管理类中提供对原始资源的访问

    • 智能指针提供了get成员

    16 成对使用new和delete时要采取相同形式

    • 使用一条new表达式时,实际执行了三步操作
      • new表达式调用标准库中的operator new(或operator new[])函数,该函数分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象(或对象的数组)
      • 编译器针对此内存调用一个或多个构造函数来构造对象,并为其传入初始值
      • 对象被分配空间并构造完成,返回一个指向该对象的指针
    • delete则执行两步操作
      • 先调用一个或多个析构函数
      • 编译器调用标准库中的operator delete(或operator[])函数释放内存
    • delete时要采用和new相同的形式,否则会造成未定义错误
    std::string str1 = new std::string;
    std::string str2 = new std::string[100];
    delete str1;
    delete [] str2;
    
    • 注意typedef时可能会隐藏创建的是数组这一信息
    typedef std::string Arr[4];
    
    std::string* p = new Arr; // Arr是一个数组
    delete [] p; // 必须匹配数组对应的delete形式
    

    17 以独立语句将newed对象置入智能指针

    • 假设有如下函数
    int f();
    void g(shared_ptr<A> p, int f);
    g(shared_ptr<A>(new A), f());
    
    • 编译器产生g的结果之前必须先计算传递的实参,于是在调用g之前,编译器必须先创建代码完成三件事
      • 调用f
      • 执行new A表达式
      • 调用shared_ptr构造函数
    • 完成顺序与编译器的实现有关,但new一定执行于shared_ptr构造之前。对f的调用可以排在第一第二或第三,如果排在第二,最终顺序是
      • 执行new A表达式
      • 调用f
      • 调用shared_ptr构造函数
    • 如果调用f发生异常,则new A返回的指针还未置于shared_ptr就丢失了,于是发生了资源泄露
    • 解决方法很简单,分离语句,以独立语句将new出来的对象存储在智能指针中
    shared_ptr<A> p(new A);
    g(p, f);
    

    相关文章

      网友评论

        本文标题:【Effective C++(3)】资源管理

        本文链接:https://www.haomeiwen.com/subject/ljhigxtx.html