13:以对象管理资源

  • 资源获取即初始化(RAII):使用析构函数确保资源被释放
  • 复制时使用移动语义,移交资源的所有权
  • 背景:使用动态内存分配时,很容易忘记delete,尤其是程序在中间退出(比如if判断后return)
  • RAII(Resource Acquisition Is Initialization)资源获取即初始化:
    • 资源的有效期与持有资源的对象的生命周期严格绑定(即获取资源的时候要通过构造函数初始化)
    • 对象独占资源
    • 即让编译器在每个退出的分支上,对象都进行析构,从而释放资源
    • 使用模板更加方便
  • 移交所有权
    • 背景:如果两个指针同时指向一个资源,会析构两遍;因此RAII类独占资源(类似unique_ptr
    • 在RAII类中,将拷贝相关的函数设置为=delete,RAII无法进行拷贝
    • 因此只能通过移动构造函数使用std::move进行移交所有权
  • 如何把RAII类作为函数的参数
    • 值传递:各位caller,我不要ownership了,请拿走
    • 非const引用传递:拿不拿走都行,提前商量好(不推荐)
    • const引用传递:可以拿走用一下,但是ownership还是我的
    • 右值引用:同第二条,无法确定caller是否拿走了ownership
  • C++98与C++11
    • C++98中std::auto_ptr类似于C++11中std::unique_ptr,但是std::unique_ptr不允许所有权被转移
    • C++98中std::tr1::shared_ptr类似于C++11中std::shared_ptrweak_ptr只是拥有资源的使用权而非所有权,因此不占用引用计数,可以解决环状引用的问题
  • 梳理:RAII作为一种管理资源的方式(或思想),早期使用auto_ptr作为解决方案,C++11之后使用unique_ptr和move语义作为解决方案
  • 参考:

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

复制RAII对象必须一并处理资源的copy行为

  • copy行为的不同情况:
    • 大部分情况下,对RAII对象的复制操作本身就不合法
    • 对底层资源使用引用计数法(shared_ptr
    • 复制底层资源(行为像值,进行深拷贝)
    • 转移资源所有权(unique_ptr

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

  • 将RAII对象转换为对资源的直接访问
    • 通过显示转换:提供一个get()函数返回智能指针内部的原始指针
    • 通过隐式转换
      • 像使用原始指针一样使用智能指针,比如智能指针一样可以使用->访问成员
      • 直接访问原始指针:在RAII类内实现返回原始指针的类型转换运算符

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

  • new一个对象,使用delete释放;new一个数组,使用delete []进行释放
    • delete []表示知道释放的是数组,读取数组元素数量,从而多次调用析构函数
  • 尽量避免对数组使用typedef,此时在delete时很容易出现混淆:用delete还是delete[],可以的话可以使用std::vector等容器

17:以独立语句将new的对象置入智能指针

  • 背景:编译器可能对单一语句中的执行顺序进行重新调整
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    int priority() {}
    void func(std::shared_ptr<MyResource> sp, int priority) {}
    
    func(std::shared_ptr<MyResource>(new MyResource), priority());
    /*
    该语句的执行顺序可能是:
        MyResource* tmp_ptr = new MyResource;
        int priority = priority();
        std::shared_ptr<MyResource> sp = std::shared_ptr<MyResource>(tmp_ptr);
    */
    
    • 如果int priority = priority();执行失败,则tmp_ptr指向的临时资源无法被释放,发生内存泄漏
    • 根本原因是:资源被创建和资源被转换成资源管理对象有时间差,中间可能有干扰
  • 解决方法:以独立语句将new的对象置于智能指针中,因为编译器无法对跨语句的操作进行调整
    1
    2
    
    std::shared_ptr<MyResource> sp(new MyResource);
    func(sp, priority());