假设现在我们有一个函数用来处理程序的优先级,另一个函数用来在某个动态分配所得的对象上进行一些带有优先级的处理:
int priority();
void processObject(std::shared_ptr<Object> obj, int priority);
由于我们时刻牢记“使用对象管理资源”的智慧铭言,因此决定对动态分配获得的对象使用智能指针。
现在考虑如何来调用processObject
函数:
processObject(std::shared_ptr<Object>(new Object), priority());
令人惊讶的是,虽然我们在这里使用了RTTI
对象来管理资源,这条语句仍然可能会导致资源泄露。
编译器在处理上面这条语句时,必须首先核算即将被传递的各个实参;上述的第二个实参只是一个单纯的priority
函数调用,但是第一个实参却由两部分组成:
- 执行
new Object
语句。 - 执行
std::shared_ptr
的构造函数。
于是我们在调用processObject
之前,编译器必须要进行如下操作:
- 调用
priority
函数。 - 执行
new Object
语句。 - 调用
std::shared_ptr
的构造函数。
问题在于,C++编译器应该以什么样的次序来完成这些事情呢?弹性很大。这和Java
或C#
这类编程语言不同,这两种高级语言总是以特定的次序完成函数参数的核算。但是对于C++而言,我们仅能保证:new Object
一定会在shared_ptr
的构造函数之前被执行,而priority
函数的调用位置则可以是任意的:它可能在上面两条语句的中间被执行,这完全取决于编译器。
一旦这种情况发生,那么就要思考:如果priority
函数抛出了异常,会发生怎样的情况?此时new Object
返回的指针会遗失,因为它尚未来得及被放入RTTI
对象中;也就是说,但就这一条语句,也是有可能发生资源泄露的。
避免这个问题的方式很简单:将语句分离开,分别写出new Object
、将指针放入RTTI
对象的语句和函数调用语句
std::shared_ptr<Object> obj(new Object);
processObject(obj, priority());
这样,编译器就无法对这些语句进行重新排列了,只有按照我们要求的合理的次序依次执行,从而避免了资源泄露。
【注意】
使用独立的一条语句将底层资源放入RTTI
对象中;如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。