========================
Effective C++ 资源管理类(resource-managing classes)
书作者:Scott Meyers
原笔记作者:Justin
========================
Item 13 :以对象管理资源
---------------------------------------
tag:auto_ptr shared_ptr RAII RSCP 智能指针
要利用对象来管理资源(可以是分配出来的内存、互斥量等)。这样可以避免因为写代码时的疏忽而导致的资源流失(可以是内存的泄漏,也可能是忘了解锁)。
将释放资源的代码放在对象的析构函数中,只要对象被析构,就可以保证资源被完整释放。
C++的析构函数自动调用机制(C++’s automatic destructor invocation)能够保证当对象脱离控制时该对象的析构函数被调用(比如一个对象在某函数内部定义,那么在函数执行结束后,这个对象的析构函数会被自动调用以销毁此对象)。
是个好办法,至少对我这样时常丢三落四的人来说是个福音。何况这样的对象大多数情况下不用自己写,auto_ptr(智能指针, smart pointer的一种)就可以胜任。
std::auto_ptr<void *> pMem(AllocAndInitMemory());
高亮的部分即说明了auto_ptr的用法,也附带示范了所谓的RAII(Resource Acquisition Is Initialization)资源取得时机便是初始化时期。
这里的auto_ptr就像是个陪女友逛街的可怜家伙,女朋友(用户)说要什么,auto_ptr就乖乖的去买来(申请),想要用的时候就赶紧拿出来给女友用,哪怕她忘了曾经买了某件衣服,auto_ptr还是会老老实实地送回家里,不用担心会弄丢或是搞脏。嗯,有点做上帝的感觉吧?
不过,没有什么是十全十美的。大师马上就说到了用对象管理资源的问题,好吧,应该说是需要注意的地方:
首先不能用一个以上的对象,比如说auto_ptr,管理同一个资源。这个很好理解,就像一个漂亮的女孩子脚踏多条船(同时定义了多个auto_ptr),但是她必须要小心,不能让多个对象参加同一个约会:今天逛街的时候喊上了小张就不能叫小王,明天一起吃饭如果约了小李就别再和小赵定时间:一个资源一次只能让一个对象来管理。
这样的规则用在auto_ptr上就是书中奇怪的“=”行为:两个auto_ptr对象A和B,A=B运算的结果是A接管了B管理的资源,B指向的是null。(如果记不起来了,具体代码见书)这样的行为很像是传递:小李陪女朋友逛完街后,送她到人民广场,在那里小陈约好了和她一起看电影。到了人民广场之后小陈搂着女人开心的走了,只剩下小李一个孤单的背影,杯具啊杯具……
为了避开这种尴尬且极易被误解的“=”操作,出现了以shared_ptr为代表的reference-counting smart pointer(RCSP)(引用计数型智能指针?)。它们可以“共事一夫”,由一个公用的reference counter来计数,某个shared_ptr在准备抛弃一个资源时先参考reference counter的值,如果非零,说明还有其他的“姐妹”在罩着资源,不需要也不可以释放该资源;如果值为零,说明当前只有她一个人了,于是真正的担当起smart pointer的责任:释放资源。
std::shared_ptr<void *> pMem(AllocAndInitMemory());
重点不在于是用auto_ptr还是shared_ptr还是其他的什么东东,而是要领会精神——使用对象来管理资源。比如说用string对象来管理一个字符串而不是手工申请一片内存然后用个指针“吧唧”粘上去就开始用了。
不能再动态分配而得的 array 上使用 auto_ptr 或 tr1::shared_ptr,因为两者在析构的时候调用的是delete而不是delete[].
要拥有针对数组而设计的,可用 boost::scoped_array 和 boost::shared_array classes.
Item 14: 在管理类中小心 coping 行为
--------------------------------------------
tag: RAII
RAII守则:资源在构造期间获得,在析构期间释放。
RAII对象被复制:
·不允许拷贝。当资源本身不能复制时,对象可以说“不”。怎么做?回到Item6……
·使用Reference-Count(引用计数),可以用上节说到的shared_ptr来干这个事,这里顺带介绍了shared_ptr提供的一个接口:一个可以在构造对象时定义的delete操作:如果对象是内存就是释放,如果对象是锁就是解锁。
·直接复制。别人有什么,你就直接原封不动也复制一份。如果是内存的话说得过去,如果是锁,我想还是不能这样乱用哈。
·移交所有权。这个不算是真正意义的复制,移交手续而已。最典型的例子就是auto_ptr的复制行为。
Item 15: 在资源管理类中提供对原始资源的访问
-------------------------------------------------
tag:返回原始资源
·在使用对象管理资源的同时也要留出接口给那些需要绕过对象而直接访问资源的人。
写个函数暴露出指向资源的指针就可以。书里讲得更多的是用怎样的函数:
显式转换(explicit conversion)
可以实现一个get函数,或是*、->运算,返回指向资源的指针。
隐式的转换函数(implicit conversion),
但是个人觉得实际工作中应该是不提倡这样做的,因为隐式的转换极有可能发生在编程者没有意识的情况下,导致后面的代码出错。
class Font {
public:
// ..
// implicit conversion function
operator FontHandle() const { return f; }
// ..
};
上面代码的应用如下,f本身为Font类型,(changeFontSize第一个参数为FontHandle),但是由于隐式转换,类型变成了FontHandle。
Font f(getFont());
int newFontSize;
//..
// implicitly convert Font to FontHandle
changeFontSize(f, newFontSize);
为了能兼容更多的API,需要留出接口提供对资源的直接访问。
隐式或显示主要取决于RAII class 被设计执行的特定工作,以及它被使用的状况。
Item 16 : 成对使用new和 delete时采用相同形式
---------------------------------------------------
tag: new delete
·用new分配一个内存对象时,语法格式是new a;用delete释放一个内存对象时,语法格式是delete a;
·用new分配一组内存对象时,语法格式是new a [num_of_elem]; 用delete释放一组内存对象时,语法格式是delete [] a;
new或是delete包含了两个阶段:
new:申请并分配内存空间;调用构造函数构造即将使用空间的对象
delete:调用析构函数析构使用空间的对象;释放内存
分配内存给一组对象的时候,编译器一般会在这一片内存前端(或是其他什么地方)插入一小段信息,用来标明这片内存是给多少个对象的,然后反复调用构造函数来创建这一组对象。当用delete []的时候,释放内存的操作就会以该信息为依据,反复调用对象的析构函数对这组对象进行释放。(下面的[n]就是这段信息)
[n][MEM]
而如果只是分配内存给一个对象,这段信息就不存在了。直接在这片内存上应用析构函数。
于是用delete []去释放new的内存,或是用delete去释放new []的内存,都会造成不可预计的后果。
Item 17 : 将new语句单独写
----------------------------------
tag: newed
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());这行语句有问题,这个复杂的参数表包含了三个动作:
·new 一个 Widget
·用new的Widget做为参数执行share_ptr的构造函数
·执行priority
C++的某个编译器可能为了效率而自作主张,导致这三个动作的执行顺序是不确定的!因此上面的动作执行顺序可能是这样的:
·new 一个 Widget
·执行priority
·用new的Widget做为参数执行share_ptr的构造函数
这个时候如果priority的执行出错而引发异常,就会发生内存泄漏(Memory Leak),因为new出来的Widget再也无法跟踪了。
而解决方法也很简单,不要妄图一行写完所有程序,分开来老老实实写就是了:
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());
VC++下不会。
posted on 2010-03-15 22:48
Euan 阅读(494)
评论(0) 编辑 收藏 引用 所属分类:
C/C++