假设我们有一个函数用来展示处理的优先级,还有一个函数,它能够根据当前优先级的设置,为一个动态分配的Widget做一些处理:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
一定要时刻记住“使用对象管理资源”(参见条目13)。此处,processWidget对其需要处理的动态分配的Widget使用了一个智能指针(在这里是一个tr1::shared_ptr)。
下面是对progressWidget的一次调用:
processWidget(new Widget, priority());
请稍等,不要试图这样调用。这将不会通过编译。tr1::shared_ptr的构造函数中包含一个原始指针,这个构造函数应为explicit的,于是便不存在从“new Widget”语句返回的原始指针到processWidget所需的tr1::shared_ptr的隐式转换。然而下边的代码将顺利通过编译:
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
看上去有些令人吃惊,尽管我们时时处处都使用对象来管理资源,但是这里还是有可能泄漏资源。了解其中的原由对深入理解是有一定启发性的。
在编译器能够生成对processWidget的调用之前,它必须对传入的参数进行预先的处理。第二个参数仅仅调用了一个函数priority,但是第一个参数(“std::tr1::shared_ptr<Widget>(new Widget)”)包含两部分:
运行“new Widget”语句
调用tr1::shared_ptr的构造函数
因此,我们说在processWidget可以被调用之前,编译器必须自动生成代码来解决下面的三件事情:
l 调用priority。
l 执行“new Widget”。
l 调用tr1::shared_ptr的构造函数。
C++编译器对于这三项任务完成的顺序要求得很宽松。(这一点与Java和C#这类语言很不一样,这类语言中的函数参数总是以一个特定的顺序得到预处理。)由于“new Widget”语句运行的结果是一个参数的形式传递给tr1::shared_ptr的构造函数的,因此它必须在tr1::shared_ptr的构造函数被调用之前得到执行。但是调用priority的工作可以放到第一,第二,也可以放在最后。如果编译器决定第二个处理它(这样可以使编译器生成的代码更高效),我们就会得到这样的执行序列:
1. 执行“new Widget”。
2. 调用priority。
3. 调用tr1::shared_ptr的构造函数。
但是请想象一下:如果调用priority时抛出了一个异常的话,将会发生些什么。在这种情况下,由“new Widget”返回的指针将会丢失。这是因为这一指针并不会保存在tr1::share_ptr中,然而我们原本还期望利用tr1::shared_ptr来避免资源泄露。这种情况下调用processWidget可能会造成资源泄漏。这是因为:在资源被创建(通过 new Widget)以后和将这个资源转交给一个资源管理对象之前的这段时间内,有产生异常的可能。
防止这类问题发生的办法很简单:使用单独的语句,创建Widget并将其存入一个智能指针,然后将这个智能指针传递给processWidget:
std::tr1::shared_ptr<Widget> pw(new Widget);
// 在一个单独的语句中创建Widget
// 将其存入一个智能指针
processWidget(pw, priority()); // 这样调用就不会泄漏了。
这样是可行的,因为编译器为多行语句安排执行顺序要比单一的语句时严格得多。由于这段改进的代码中,“new Widget”语句以及tr1::shared_ptr的构造函数将在单独的语句中得到调用,而对priority的调用在另一个单独的语句中,所以编译器就没有机会将对priority的调用挪动到“new Widget”语句和tr1::shared_ptr的构造函数之间了。
时刻牢记
l 在智能指针中的由new创建的对象要在单独的语句中保存。如果不这样做,你的程序会在抛出异常时发生资源泄漏。