Effective C++ 资源管理小结

来源:岁月联盟 编辑:猪蛋儿 时间:2012-11-13


我发现,第一遍读书总是不能清楚的认识到问题的本质,我们还需要回过头去总结,因此,我决定以后每读一部分的书,写一点的总结,一方面可以让自己整理知识,另一方面方便以后对比思想。
资源管理
前言
资源泄露就是程序中常见的事情,这一章主要就是告诉我们如何去消除资源管理问题,值得注意的是,这里的资源不仅是指动态分配内存,其他常见的资源还包括数据库的连接,网络sockets,还有互斥锁等,我们在用完这些资源之后,必须将他们还给系统。尤其是发生异常、程序员维护软件时,会产生这种问题。

条款13:以对象管理资源
先看段代码:
[cpp] 
void test() 
{        
     int *t = new int;//获取资源        
     ...        
     delete t;//释放资源 


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

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

条款14:在资源管理类中小心copying行为
像上一条款说的,有的时候auto_ptr不适合资源管理类,我们需要自己创建资源管理类。
[cpp] 
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发生了复制,会发生什么?

Lock m1(&m);//锁定
Lock m2(m1);//复制
导致的恶果就是 将会对同一个资源释放两次。

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

条款15:在资源管理类中提供对原始资源的访问
我们需要面对的一个问题就是,现实中很多的API的参数直接涉及到资源,而我们把资源放在资源管理类中,因此我们需要提供对原始资源的访问。
[cpp] 
auto_ptr<int> t(new int); 
int test(int *t);//直接涉及资源 
[cpp] view plaincopy
<pre></pre><span style="font-size:18px"><span style="font-family:Microsoft YaHei"></span></span> 
<pre></pre> 
<pre></pre> 
<pre></pre> 
<pre></pre> 
<pre></pre> 
我们有两种方法来达到目标:显示转换和隐式转换
1.显示转换
通常的做法就是提供一个get()成员函数,返回资源。auto_ptr和shared_ptr就是这么做。
但是这么做的后果,就是会导致频频使用get()。
2.隐式转换
提供隐式转换函数
[cpp]
class Font { 
 
public: 
 
  ... 
 
  operator FontHandle() const { return f; } // 进行隐式转换的函数 
  ... 
 
}; 
但是会导致错误问题的增多:
[cpp] 
<pre></pre> 
<pre></pre> 
<pre></pre> 
<pre></pre> 
<pre></pre> 
FontHandle f2 = f1;//f1是一个Font对象
本来我只是想复制一个Font,但是不小心写成了FontHandle,这时候f2会隐式转换成FontHandle,再复制

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

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

条款17:以独立语句将new对象置入智能指针
[cpp]
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 的构造函数。

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

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