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发生了复制,会发生什么?
- Lock m1(&m);//锁定
- 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()); // 这样调用就不会泄漏了。、