为什么在所有情况下都允许指向 shared_ptr 构造的原始指针?

Why is raw pointer to shared_ptr construction allowed in all cases?

我正在阅读 Top 10 dumb mistakes to avoid with C++11 smart pointer。 第 5 号显示:

Mistake # 5 : Not assigning an object(raw pointer) to a shared_ptr as soon as it is created !

int main()
{
    Aircraft* myAircraft = new Aircraft("F-16"); 
    shared_ptr<aircraft> pAircraft(myAircraft);
    ...
    shared_ptr<aircraft> p2(myAircraft); 
    // will do a double delete and possibly crash
}

推荐如下:

Use make_shared or new and immediately construct the pointer with it.

好的,毫无疑问,问题和建议。 但是我对 shared_ptrdesign 有疑问。 这是一个很容易犯的错误,shared_ptr 的整个“安全”设计可能会被非常容易检测到的误用所抛弃。

现在的问题是,是否可以通过 shared_ptr 的替代设计轻松解决此问题,其中来自原始指针的唯一构造函数是来自右值引用的构造函数?

template<class T>
struct shared_ptr{
    shared_ptr(T*&& t){...basically current implementation...}
    shared_ptr(T* t) = delete; // this is to...
    shared_ptr(T* const& t) = delete; // ... illustrate the point.
    shared_ptr(T*& t) = delete;
    ...
};

这样 shared_ptr 只能从 new 或一些工厂函数的结果中初始化。

这是库中 C++ 语言的未充分利用吗?如果这很可能是误用,那么从原始指针(左值)引用获取构造函数有什么意义?

这是历史事故吗? (例如 shared_ptr 是在引入 r 值引用之前提出的,等等)向后兼容?

(当然有人会说 std::shared_ptr<type>(std::move(ptr)); 这更容易捕捉,并且如果确实有必要的话也是一种解决方法。)

我是不是漏掉了什么?

指针很容易复制。即使您限制为 r 值引用,您仍然可以轻松制作副本(例如当您将指针作为函数参数传递时),这将使安全设置无效。此外,您将 运行 陷入模板中的问题,在模板中您可以轻松地将 T* constT*& 作为类型,并且类型不匹配。

因此,您提议在没有显着提高安全性的情况下制定更多限制,这可能是它最初不在标准中的原因。

make_shared的重点是原子化共享指针的构造。假设您有 f(shared_ptr<int>(new int(5)), throw_some_exception())。标准不保证参数调用的顺序。允许编译器创建一个新的 int,运行 throw_some_exception,然后构造 shared_ptr,这意味着您可以泄漏 int(如果 throw_some_exception 实际上抛出异常)。 make_shared只是在自身内部创建对象和共享指针,不允许编译器更改顺序,因此很安全。

在很多情况下您可能无法调用 make_shared()。例如,您的代码可能不负责分配和构造有问题的 class。出于各种原因,某些 C++ 代码库中使用了以下范例(私有构造函数 + 工厂函数):

struct A {
  private:
     A();
};

A* A_factory();

在这种情况下,如果您想将从 A_factory() 获得的 A* 粘贴到 shared_ptr<> 中,则必须使用采用原始指针的构造函数make_shared().

其他一些例子:

  • 您想使用 posix_memalign() 为您的类型获取对齐内存,然后使用调用 free() 的自定义删除器将其存储在 shared_ptr<> 中(此用例很快就会消失当我们向语言添加对齐分配时!)。
  • 您想将指向使用 mmap() 创建的内存映射区域的指针粘贴到带有调用 munmap() 的自定义删除器的 shared_ptr<> 中(此用例将在我们得到了 shmem 的标准化设施,我希望在接下来的几个月里继续努力。
  • 您想使用自定义删除器将由分配的指针粘贴到 shared_ptr<> 中。

我对 shared_ptr 的设计没有任何特别的见解,但我认为最可能的解释是所涉及的时间表使这不可能:

shared_ptr 在 C++11 中与右值引用同时引入。 shared_ptrboost 中已经有一个有效的参考实现,因此可以预期它会相对快速地添加到标准库中。

如果 shared_ptr 的构造函数仅支持从右值引用构造,它将无法使用,直到编译器 实现了对右值引用的支持。

那时,编译器和标准的开发更加异步,因此可能需要 才能实现所有编译器的支持(如果有的话)。 (导出模板在 2011 年人们记忆犹新)

此外,我认为标准委员会会对没有参考实现的 API 进行标准化感到不自在,甚至在标准发布后才能获得参考实现。