《Effective C++》(三)

来源:互联网 发布:五谷磨房固元膏淘宝 编辑:程序博客网 时间:2024/04/26 05:55

  • 资源管理
    • 条款13以对象管理资源
    • 条款14在资源管理中小心copying行为
    • 条款15在资源管理类中提供对原始资源的访问
    • 条款16成对使用 new 和 delete 时要采用相同的形式
    • 条款17以独立语句将 newed 对象置入智能指针

3 资源管理

什么是资源——一旦使用,就必须还给系统的东西。C++程序员最长使用的资源就是动态分配内存(因为如果你分配内存却不曾归还,会导致内存泄露),但显然内存只是你必须管理的众多资源之一。其他常见资源:文件描述器、互斥锁、图形界面中的字型和笔刷、数据库连接 以及 网络sockets。

条款13:以对象管理资源

1.常常在函数开头new一个新对象,在函数结尾delete之。但是由于过早return、循环中的continue或goto、抛出异常等动作,可能指向不到函数结尾的delete
2.将资源放进对象内,倚赖C++的析构函数自动调用机制确保资源被释放
3.“以对象管理资源”的两个关键想法:获得资源后立刻放进管理对象内,即“资源取得时机便是初始化时机”(RAII);管理对象运用析构函数确保资源被释放。
4.auto_ptr 是个类指针(pointer-like)对象,也就是所谓的——智能指针,其析构函数自动对其所指对象调用 delete。其有个不寻常的性质:若通过 copying 函数(copy构造函数 或 copy assignment 操作符 )复制它们,它们会变成null,而复制所得的指针将取得 资源的唯一拥有权。

std::auto_ptr<Investment> pInv1(createInvestment() );    // pInv1 指向 函数返回的对象  std::auto_ptr<Investment> pInv2(pInv1);    // 现在pInv2指向那个对象,pInv1 为 null  pInv1 = pInv2;    // 现在 pInv1指向那个对象,pInv2为 null

5.RCSP(reference-counting smart pointer,引用计数型智慧指针)也是个智能指针,如TR1的 tr1::shared_ptr, 它可以持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源,这种行为类似于垃圾回收( garbage collection),不同的是 RCSPs无法打破环状引用。
6.auto_prt 和 tr1::shared_ptr 两者都在析构函数内做 delete 而非 delete[] 动作,所以动态分配而得到的 array上使用 auto_ptr 或 tr1::shared_ptr 是个错误的选择,遗憾的是这样也可以通过编译。
7.小结:
<1>为防止资源泄漏,请使用RAII 对象,它们在构造函数中获得资源并在析构函数中释放资源。
<2>两个常被使用的RAII类 分别是 auto_ptr 和 tr1::shared_ptr。后者通常是较佳的选择,因为它 copy 行为比较直观。若选择 auto_ptr,复制动作会使它(被复制物)指向null

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

1.建立自己的资源管理类
处理类型为 Mutex 的 互斥器对象时常用的以下两个函数

void lock(Mutex* pm);    // 锁定互斥器  void unlock(Mutex* pm);    // 解锁互斥器

为确保我们不会忘记将一个被锁住的 Mutex 解锁,可能会希望建立一个类来管理它,让类在析构期间释放它:

class Lock  {  public:      explicit Lock(Mutex* pm) : mutexPtr(pm)      {  lock(mutexPtr);  }      ~Lock()  {  unlock(mutexPtr);  }  private:      Mutex *mutexPtr;  };

用法:

Mutex m;    // 定义所需要的互斥器  ...  {    // 建立一个区块来定义临界区      Lock m1(&m);    // 锁定互斥器      ...  }    // 在区块最末尾,自动解除互斥器锁定

2.禁止RAII对象复制。将copying操作声明为 private

class Lock : private Uncopyable  {  public:      ...    // 同前一样  };

3.对底层资源祭出“引用计数法”(reference-count)。将成员变量定义为 tr1::shared_ptr 类型

class Lock  {  public:      explicit Lock(Mutex* pm) : mutexPtr(pm,unlock)      {          lock(mutexPtr.get() );      }  private:      std::tr1::shared_ptr<Mutex> mutexPtr;    // 使用 shared_ptr 替换 raw pointer  };

而且本例中的 Lock class 不再声明析构函数。因为没有必要。
4.复制底部资源。复制资源管理对象,应该同时也复制其所包含的资源,即复制对象时,进行“深度拷贝”
5.转移底部资源的拥有权。 使用auto_ptr使RAII 对象被复制时资源的拥有权会从 被复制物 转移到 目标物
6.Copying函数有可能被编译器自己自动创建出来,因此除非编译器所生成版本做了你想做的事,否则你需要自己编写它们
7.小结:
<1>复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为
<2>普遍而常见的RAII 类 copying 行为是:抑制 copying 、 施行引用计数法(reference counting)。不过其他行为也都可能被实现

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

1.有时候不得不绕过资源管理对象而直接访问原始资源
2.显式转换:tr1::shared_ptr 还是 auto_ptr 都会提供一个 get成员函数,可以用来获取内部的原始指针。
3.隐式转换:(几乎)所有的智能指针都重载了指针取值操作符( operator-> 和 operator* ),它们允许隐式转换至指针底部原始指针

class Investment  {  //根类public:      bool isTaxFree()  const;      ...  };  Investment* createInvestment();  //factory函数std::tr1::shared_ptr< Investment > pi1(createInvestment());  // 令tr1::shared_ptr 管理一笔资源  bool taxable1 = !(pi1->isTaxFree() );  // 通过 operator-> 访问资源  ...  std::auto_ptr<Investment> pi2( createInvestment() );  // 令auto_ptr 管理一笔资源  bool taxable2 = !((*pi2).isTaxFree()); // 通过 operator* 访问资源

4.显示转换函数安全但是相对麻烦;隐式转换函数使用自然但有可能引发“非故意的类型转换”。是否该提供一个显式转换函数( 例如get成员函数)将RAII 类转换为底部资源,或是提供隐式转换函数,主要取决于RAII 类被设计执行的特定工作,以及它被使用的情况。最佳设计应该是坚持条款18的忠告: 让接口容易被正确的使用,不易被误用。
5.小结:
<1>APIs往往要求访问原始资源,所以每一个RAII类应该提供一个“取得其所管理资源”的办法。
<2>对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便。

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

1.使用 new 时内存被分配出来 (通过名为 operator new 的函数)同时会有一个(或更多)构造函数被调用;使用 delete 时会有一个(或更多)析构函数被调用,然后此内存才会被释放(通过 operator delete 的函数)
2.使用delete时须考虑即将被删除的指针,指向单一对象还是对象数组,因为两者内存布局不一样。
3.如果调用 new 时,用了[ ] ,你必须在对应调用delete时使用 [ ] ; 如果调用 new 时,没用[ ] ,你也不应该在用 delete时用 [ ]

std::string* stringPtr1 = new std::string;  std::string* stringPtr2 = new std::string[100];  ...  delete stringPtr1;    // 删除一个对象  delete [ ] stringPtr2;    // 删除对象数组 

4.最好不要对数组形式进行typedef动作,因为delete的使用不够清晰。可以改用C++标准程序库含有 string、vector这样的template

typedef std::string AddressLines[4];    // 每个人的地址有四行,每行类型都是 string  std::string* pal = new AddressLines;    // 它返回一个string*对象,就像new string[4] ...delete pal;    // 错误,行为未有定义!  delete [ ] pal;    // 这样才对 

5.小结: 如果你在 new 表达式中使用[],必须在相应的delete表达式中也是用[]。如果你在 new 表达式中,不使用[],一定不要在相应的delete表达式中使用[]

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

1.不推荐如下格式:

processWidget( std::tr1::shared_ptr<Widget>( new Widget ) , priority() );  

该行代码要执行三件事:①调用 priority;②执行 ” new Widget ” ;③调用 tr1::shared_ptr 构造函数。而这三步的执行顺序有一定弹性,假设执行顺序为②->①->③,万一中间①调用priority出现错误,导致异常,则之前②中返回的指针将会丢失,因为它尚未被置入 tr1::shared_ptr,从而引发资源泄漏 。
2.推荐使用如下分离语句:

std::tr1::shared_ptr<Widget> pw( new Widget )  //在单独语句内以智能指针存储newed所得对象processWidget(pw,priority() );//这个调用动作绝对不至于造成资源泄漏

因为,C++ 编译器无法跨越语句进行重新排列,只能在语句内重新排列
3.小结:以独立语句将 newed 对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏

原创粉丝点击