Effective C++ 资源管理总结

Effective C++ 资源管理小结


我发现,第一遍读书总是不能清楚的认识到问题的本质,我们还需要回过头去总结,因此,我决定以后每读一部分的书,写一点的总结,一方面可以让自己整理知识,另一方面方便以后对比思想。

资源管理

前言

资源泄露就是程序中常见的事情,这一章主要就是告诉我们如何去消除资源管理问题,值得注意的是,这里的资源不仅是指动态分配内存,其他常见的资源还包括数据库的连接,网络sockets,还有互斥锁等,我们在用完这些资源之后,必须将他们还给系统。尤其是发生异常、程序员维护软件时,会产生这种问题。

条款13:以对象管理资源

先看段代码:
void test()
{       
     int *t = new int;//获取资源       
     ...       
     delete t;//释放资源
}


如果我们在"..."中产生了异常,或者存在return,就可能导致资源的泄露。也许你会说,我很谨慎,我不会让自己的代码出现这种问题。好吧,那如果这是一个项目,之后这段代码可能会被维护人员修改,他可能会在“...”中加入异常,或者return。也许,你又会说,你会将这段代码需要注意的地方写入文档,可是我觉得我们应该把自己当成“客户”(这里指使用这段代码和维护代码的人),我们需要为自己着想,如果我们能做好,就不应该甩手把麻烦的事情留给别人。
为防止以上的现象,我们可以将资源放入对象中,依赖c++的“析构函数自动调用机制”确保资源被释放。
void test
{
        auto_ptr<int> t (new int);//注意初始化方式
        ...
}//auto_ptr的析构函数自动delete t

auto_ptr是个只能指针,析构函数会自动对其所指的对象调用delete。
书中推荐使用auto_ptr和shared_ptr(我在这里就不在介绍这个,有兴趣的可以自己google),但是我们也可以自己写资源管理类,但是其中涉及到需要考虑的细节,将在后面的条款讨论。


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

像上一条款说的,有的时候auto_ptr不适合资源管理类,我们需要自己创建资源管理类。
void lock(Mutex* pm);//锁定pm所指的互斥器 
void unlock(Mutex* pm);//将互斥器接触锁定
class Lock 
{  
public:  
  explicit Lock(Mutex* pm):mutexPtr(pm)  
  {  
    lock(mutexPtr);//获得资源  
  }  
  ~Lock()  
  {  
    unlock(mutexPtr);//释放资源  
  }  
private:  
  Mutex *mutexPtr;  
};  
如上,我们将会为Mutex自动释放资源,但是我们需要考虑的一个问题就是,如果Lock发生了复制,会发生什么?
  1. Lock m1(&m);//锁定 
  2. Lock m2(m1);//复制 
导致的恶果就是 将会对同一个资源释放两次。

那我们面对这样的问题,该如何选择:
1.禁止复制
当资源管理类对象被复制时,如果不合理,我们就会选择禁止复制。可以将copying操作声明为private而不实现它,达到禁止的目的。(条款6中详细说了)
2.对底层资源祭出“引用计数法”
用一个变量保存引用个数,当引用个数为0时,才销毁它。这就是shared_ptr的做法(此外,shared_ptr允许指定删除器,当引用次数为0时,便会调用这个删除器)
3.复制底部资源
也就是“深度拷贝”,不仅指针会被制作出一个复件,而且会创建一个新的内存。
4.转移底部资源的拥有权
简单点说,就是拥有权会从被复制的对象转移到复制的对象,而被复制的对象失去所有权,这是auto_ptr所实现的。
比如
auto_ptr<int> t (new int);
auto_ptr<int> a = t;//所有权从t转向a,t将会指向NULL

条款15:在资源管理类中提供对原始资源的访问
我们需要面对的一个问题就是,现实中很多的API的参数直接涉及到资源,而我们把资源放在资源管理类中,因此我们需要提供对原始资源的访问。
auto_ptr<int> t(new int);
int test(int *t);//直接涉及资源






我们有两种方法来达到目标:显示转换和隐式转换
1.显示转换
通常的做法就是提供一个get()成员函数,返回资源。auto_ptr和shared_ptr就是这么做。
但是这么做的后果,就是会导致频频使用get()。
2.隐式转换
提供隐式转换函数
class Font {

public:

  ...

  operator FontHandle() const { return f; } // 进行隐式转换的函数
  ...

};
但是会导致错误问题的增多:





FontHandle f2 = f1;//f1是一个Font对象
本来我只是想复制一个Font,但是不小心写成了FontHandle,这时候f2会隐式转换成FontHandle,再复制

我的个人意见是,根据需要选择方法。

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

这个没什么好说的,简单的说,就是如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[] 。
特别要注意的就是使用typedef时,
typedef std::string AddressLines[4];  
std::string* pal = new AddressLines; //注意,“new AddressLines”返回一个string*,就像“new string[4]”一样  
delete pal; //行为未定义  
delete [] pal; //很好


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

processWidget(std::tr1::shared_ptr<Widget> pw(new Widget), int priority);
可能会导致资源的泄露,为什么呢?
因为语序的问题!
这个语句总共完成了3件事情:
  • 调用 priority 。
  • 执行 “new Widget” 。
  • 调用 tr1::shared_ptr 的构造函数。
他们的操作顺序有很大的弹性,如果按以下顺序执行:

1. 执行 “ new Widget ” .

2. 调用 priority 。

3. 调用 tr1::shared_ptr 的构造函数。

std::tr1::shared_ptr<Widget> pw(new Widget); // 在一个单独的语句中创建 Widget  
                                             // 并存入一个智能指针  
processWidget(pw, priority());     // 这样调用就不会泄漏了。、

总结:这一章就这么结束了,感觉学到蛮多的,我发现在总结的过程中,将作者的话转换出来,也能够收获一些东西,以后要写写。