假设我们有如下函数:
int priority(); //返回处理优先级
void processWidget(std::shared_ptr<Widget> pw, int priority); //处理对象根据不同优先级
现在用如下的语句调用这些函数:
processWidget(new Widget, priority());
这句调用必然会导致编译器报错,因为除非定义过隐式转换的运算符,裸指针对象不能被隐式转换为智能指针对象,下面才是语法正确的调用方式:
processWidget(std::shared_ptr<Widget>(new Widget), priority());
重点来了,即使你在这里使用了智能指针,内存泄漏依然有发生的可能。
震惊?我们先来看编译器的工作原理。编译器在生成对processWidget函数的调用之前,必须先解析其中的参数。这里我们有两个参数,分别是智能指针的构造函数和整型的函数priority()。在调用智能指针的构造函数之前,编译器又必须先要解析其中的new Widget语句,因此解析该函数的参数分为三步:
- 调用priority()函数
- 执行new语句
- 调用智能指针构造函数
不像Java或者C#的编译器只会以固定的顺序解析参数,C++编译器多种多样,而且根据优化选项的不同,编译器可能会改变这三步的执行顺序,以此利用指令流水线停顿的周期(pipeline stall),获得更高效率的代码。但不论如何改变,底线是new语句必须提前于智能指针的构造函数,所以假设我们的编译器生成的机器代码实际执行了这样的顺序:
- 执行new语句
- 调用priority()函数
- 调用智能指针函数
如果priority()函数抛出了异常呢?那么从new语句动态分配的资源在到达智能指针构造函数之前就会泄露了。解决方法也很简单,使用一个单独的语句来创建智能指针对象:
std::shared_ptr<Widget> pw(new Widget); //放在单独的语句里
processWidget(pw,priority()); //这样就不会泄露资源了
编译器是逐语句编译的,通过使用一个单独的语句来构造智能指针对象,编译器就不会随意改动解析顺序,保证了生成的机器代码顺序是异常安全的,以及这样的代码写出来也更加美观。
总结:
- 用一个单独的语句把裸指针储存到智能指针中。否则资源泄漏可能就会这么意想不到地发生了。