Effective C++条款17:以独立语句将newed对象置入智能指针(Store newed objects in smart pointers in standalone statements)
《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读:
条款17:以独立语句将newed对象置入智能指针
1、使用对象管理资源仍然会出现资源泄漏的用法
假设我们有个函数返回处理程序的优先权,然后用另一个函数在某动态分配所得的Widget上进行某些带有优先权的处理
int priority();
//返回优先权
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority); //根据优先权处理对象
我们知道 条款13的至理名言是以对象管理资源,processWidget为它需要处理的动态分配对象Widget使用了智能指针(tr1::shared_ptr)。
现在考虑对processWidget函数的调用:
processWidget(new Widget, priority());
这个函数调用不能通过编译,因为在tr1::shared_ptr构造函数需要一个原始指针,但该构造函数是个explicit构造函数,无法进行隐式转换,也就是不能将“new Widget”返回的原始指针直接隐式转换为processWidget需要的tr1::shared_ptr。下面的代码将会通过编译:
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
虽然这里我们使用了对象类管理资源,但是这个调用仍然可能出现内存泄漏。如何才能出现资源泄漏呢?
2、智能指针资源泄漏情况
在编译器生成一个对processWidget的调用之前,它们必须对函数参数做一些检查。第二个参数只是调用了函数priority,但是第一个参数包含两部分:
-
执行new Widget
-
调用tr1::shared_ptr构造函数
在调用processWidget之前,编译器必须为下面的三个步骤生成代码:
-
调用priority
-
执行 “new Widget”
-
调用tr1::shared_ptr构造函数。
对于上面三个步骤的执行顺序,c++编译器被给予了很大的弹性。(这同java和c#不同,这两门语言的执行顺序固定)“new Widget”表达式必须在tr1::shared_ptr构造函数之前被调用,因为它的结果会传递给tr1::shared_ptr作为参数,但是对priority()函数的执行次序是任意的(第一个,第二个,第三个执行都可以)。如果编译器选择第二个执行(因为这样可能会生成更高效的代码),执行顺序如下:
- 执行 “new Widget”
- 调用priority
- 调用tr1::shared_ptr构造函数。
试想,如果调用priority时产生异常将会发生什么?在这种情况下,从”new Widget”返回的指针会被丢失,因为它没有存入tr1::shared_ptr中,但我们的原意是使用tr1::shared_ptr来防止资源泄漏。对processWidget的调用会使资源泄漏发生,因为在资源被创建和将资源转交给资源管理对象的时间间隔内发生了异常。
3、如何避免资源泄漏
在上面的分析中,我们可以看到在“资源创建(new)”和“资源被使用”之间如果发生了异常,那么就会造成资源泄漏。
- 解决办法:避免这类问题就是分离语句,将“创建的对象”与“放入智能指针对象”这两个步骤合成一步完成,而不是在函数调用中完成,例如,下面的函数调用就不会产生错误:
std::tr1::shared_ptr<Widget> pw(new Widget); //以单独语句存储对象
processWidget(pw, priority()); //安全调用函数
上面这种方法之所以行得通的,是因为编译器被给予更少的余地来对语句进行重新排序。在上面的代码中,我们将“new Widget”以及对tr1::shared_ptr构造函数的调用放在一个语句中,把对priority的调用放在另一个语句中,这样就不允许编译器在”new Priority”和tr1::shared_ptr构造函数之间执行priority。
5、牢记
- 以独立语句将newed对象存储于(置于)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以觉察的资源泄漏。
总结
期待大家和我交流,留言或者私信,一起学习,一起进步!