为什么boost库没有make_scoped()函数?

15
Boost的 make_shared() 函数承诺在尝试创建 shared_ptr 时具有异常安全性。
为什么没有相应于 make_shared()make_scoped()等价物? 是否存在常见的最佳实践?
以下是来自boost::scoped_ptr 文档的代码示例,它似乎不安全:
    boost::scoped_ptr<Shoe> x(new Shoe);
这行代码将按顺序执行以下三个操作:

  • Shoe分配堆内存
  • 调用Shoe的构造函数
  • 调用boost::scoped_ptr<Shoe>的构造函数

如果Shoe的构造函数抛出异常,则会导致内存泄漏。 (请参见R. Martinho Fernandes的答案)此时scoped_ptr无法处理回收,因为它尚未被构造。

这是疏忽吗?还是我没有注意到的解决方案?


这个例子是安全的,但下面这个不安全:f(boost::scoped_ptr<Shoe>(new Shoe), g());。解决这个问题的编程实践是:总是将智能指针命名为变量或成员,不要将它们构造为临时子表达式。 - aschepler
3个回答

15

scoped_ptr是在移动语义出现之前设计的,因此其设计为无法复制。因此,make_scoped是不可能实现的,因为要从函数返回一个对象,它的类型必须是可移动或可复制的。


需要明确的是:自从C++11以来,可以从函数返回不可复制、不可移动的对象:https://wandbox.org/permlink/GLIgM5nA9ROJahvk 但正如您所指出的,scoped_ptr早于c++11,这就是关键所在。 - Mariusz Jaskółka

14
如果构造函数失败,不会有内存泄漏。这是使用new时的语义之一,不涉及智能指针。
struct Foo { Foo() { throw 23; } };
new Foo(); // no memory leaked

make_shared提供的额外异常安全性是在您初始化两个shared_ptr时,并且这两个初始化不是有序的,例如在函数调用参数中的情况:

struct Bar {
    Bar(bool fail) {
        if(fail) throw 17;
    }
}
f(shared_ptr<Bar>(new Bar(true)), shared_ptr<Bar>(new Bar(false)));

由于new Bar(true)shared_ptr<Bar>(new Bar(true))new Bar(false)shared_ptr<Bar>(new Bar(false))之间没有顺序,所以下面的情况可能发生:

  1. new Bar(false)被评估并成功:内存被分配;
  2. new Bar(true)被评估但失败了:它不会泄漏由此评估产生的内存;

此时没有构造任何shared_ptr,因此在#1中分配的内存现在泄漏了。


这句话的意思是我理解你的意思了吗?new承诺捕获从构造函数中抛出的异常,释放内存,然后重新抛出异常。 - Drew Dormann
3
@Drew:是的,差不多就是这个意思。如果您感兴趣,这在标准的§5.3.4第18段中有描述。 - R. Martinho Fernandes
3
make_shared 除了创建对象外,其另一个目的(大多数实现中)是在创建对象时同时为引用计数分配内存(分配足够对象和计数器的内存块),以避免两次堆分配带来的性能损失。scoped_ptr(或C++11中的unique_ptr)不需要引用计数,因此在这方面无法从中获益。 - John5342

1
如果Shoe抛出异常,那么Shoe并没有被构造,因此scoped_ptr实际上无法做任何事情。对吧? scoped_ptr x在堆栈上,并将在作用域退出时清除。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接