item13 以对象管理资源

:::tips 通过new开辟出来的对象需要主动调用delete来销毁

  • 人为的管理可能会因为种种原因导致未释放(e.g.异常、提前的返回等等) ::: :::info 以对象管理资源:

  • 获得资源后立即放入对象,这个观念又被称为:资源取得时机便是初始化时机,即 RAII(Resource Acquisition Is Initialization)

  • 管理对象运用析构函数确保资源被释放 :::

    1. std::auto_ptr<T> pInv(xx);
    2. std::shared_ptr<T> pInv(xx);

    通过智能指针可以实现资源的管理;

  • 可以使用 auto_ptr 来管理资源,该对象析构时释放资源。需要注意auto_ptr 被复制时,原指针将变成 NULL,保证同时只有一个指针指向资源。若需要支持正常复制,则需要使用 shared_ptr

  • 需要注意 deletedelete[],对应newnew[]

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

    :::info 当一个RAII对象被复制时,会有以下两种可能: :::

  • 禁止复制 :::info 比如对于锁(lock),会把copying操作声明为私有; :::

  • 对底层资源祭出“引用计数法”

比如std::shared_ptr<template>

当然,也可以又别的选择:

  • 复制底部资源
  • 转移底部资源所有权

auto_ptr :::tips

  • 复制RAII对象必须一并复制它管理的资源,资源的copying行为决定了RAII对象的copying行为
  • 普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法。 :::

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

    在有些情况下,有的API需要直接指涉资源,这时需要资源管理类能够提供对原始资源的访问 :::tips 两种做法:

  • 显式转换

  • 隐式转换 :::

    1. class Font
    2. {
    3. public:
    4. FontHandle get() const { return f; }
    5. private:
    6. FontHandle f;
    7. }
    1. class Font
    2. {
    3. public:
    4. operator FontHandle() const { return f; }
    5. private:
    6. FontHandle f;
    7. }
    1. void changeFontSize(FontHandle f,int newSize); //函数
    2. Font f(getFont());
    3. changeFontSize(f.get(),newFontSize); //显示调用
    4. changeFontSize(f,newFontSize); //隐式:
    5. //changeFontSize(FontHandle(f),newFontSize);//相当于这样

    :::tips 一般而言显式转换更安全,隐式转换客户用起来更舒服; :::

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

    :::info 使用new会发生什么?

  • 通过operator new函数获得分配的内存(p.s.实际上还是同malloc分配的,malloc又是通过系统调用_brk和mmap开辟内存空间)

  • 针对此内存,会有一个或是多个构造函数被调用。 ::: :::tips 同理,调用delete也会执行两件事:析构&释放内存空间,但是delete不知道是否是在处理一个数组; :::

  • 调用new[],必须在相应的delete表达式中也使用[],反之如果没有用[],也不要在delete中用[]

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

    考虑这样一个函数

    1. void processWidget(shared_ptr<Widget> pw,int priority);
    2. int priority();
    3. //wrong!
    4. processWidget(new Widget,priority());

    :::tips shared_ptr构造函数需要一个原始指针,但是该构造函数是个explicit构造函数,上述的传参方式会发生隐式转换:Widget*->shared_ptr<Widget> :::

    1. processWidget(nshared_ptr<Widget>(new Widget),priority());

    编译器在调用processWidget之前要计算各个实参,所以需要创建下面的代码:

  • 调用 priority

  • 执行 new Widget
  • 调用 std::shared_ptr 构造函数

编译器能够保证 new Widgetstd::shared_ptr 之前完成,因为前者作为参数被后者调用,但是对于 priority 编译器却可以排在第一、第二甚至第三执行,假设执行顺序如下:

  • 执行 new Widget
  • 调用 priority
  • 调用 std::shared_ptr 构造函数 :::danger 当 priority 出错时,new Widget 返回的指针将会遗失,因为此时它还没被置入 std::shared_ptr 中,而造成内存泄露,解决方法也很简单,使用分离语句 :::

  • 以独立语句将newed对象存储在智能指针内,否则一旦发生异常,可能导致难以察觉的资源泄露。