0%

[Effective C++ 笔记]条款17. 以独立语句将 `new`ed 对象置入智能指针

简介

  • 以独立语句将 new 的对象置于智能指针中。否则一旦操作之间有异常被抛出有可能会导致难以察觉的资源泄漏

例子

考虑以下例子,我们有两个函数,一个得到某函数的优先级,另一个用于针对某个动态分配的 Widget 对象以给定优先级的方式进行处理:

1
2
int priority();
void ProcessWidget(std::shared_ptr<Widget> pw, int priority);

当我们传递一个动态生成的 Widget 对象至 ProcessWidget 时,我们通常有以下几种思路:

  • 直接动态生成一个 Widget 并将裸指针传入:
1
processWidget(new Widget, priority());

这种用法无法通过编译,因为 std::shared_ptr 不接受隐式转换,无法直接从 Widget* 转换为 std::shared_ptr<Widget>,所以这种方法不可行

  • 显式构造 std::shared_ptr<Widget> 再将其作为参数传入函数:
1
processWidget(std::shared_ptr<Widget>(new Widget), priority());

这种方法可以通过编译,并且看上去也没有问题,但其中存在有可能导致内存泄漏的部分。我们思考一下在调用这个函数之前编译器需要完成哪些步骤,包括以下散步:

  1. 调用 new Widget 生成裸指针
  2. 将裸指针传给 std::shared_ptr<Widget> 的构造函数构造一个智能指针
  3. 调用 priority

看上去很顺理成章,但其实编译器不保证执行这三步的步骤和上面一样!编译器只能保证 1 会发生在 2 之前,因为构造只能指针需要裸指针作为参数。但 3 是可以发生在 1 和 2 中间,这意味着执行顺序有可能如下:

1
2
3
Widget* pw = new Widget;
int p = priority();
std::shared_ptr<Widget> s_pw = std::shared_ptr<Widget>(pw);

这意味着如果在调用 priority() 的过程中出现了异常,第 3 步将不会执行,导致动态生成的 Widget 无法传给 std::shared_ptr 被管理,从而引发资源泄漏,而这种情况发生的原因是在资源被创建资源被转换成资源管理对象时有时间差,在这两个时间中间有可能会出现异常干扰。因此我们需要使用独立语句先创建智能指针,再将其传给函数:

1
2
std::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());

由于编译器无法对于跨语句的操作进行重新排列,所以我们可以很肯定的保证在 new Widgetstd::shared_ptr<Widget>() 之间没有其他操作。

结论

  • 以独立语句将 new 的对象置于智能指针中。否则一旦操作之间有异常被抛出有可能会导致难以察觉的资源泄漏

  • 完整可运行代码地址:Effective-Cpp-Reading-Note

  • Efftive C++ 阅读笔记:Effective C++