【问题标题】:Why is raw pointer to shared_ptr construction allowed in all cases?为什么在所有情况下都允许指向 shared_ptr 构造的原始指针?
【发布时间】:2016-09-14 12:21:59
【问题描述】:

我正在阅读Top 10 dumb mistakes to avoid with C++11 smart pointer。 第 5 号内容如下:

错误 #5:没有将对象(原始指针)分配给 shared_ptr 作为 一旦创建!

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
}

建议是这样的:

使用make_sharednew 并立即构造指针 它。

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

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

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++ 语言的开发不足吗?如果这很可能是误用,那么使用原始指针(左值)引用的构造函数有什么意义?

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

(当然可以说std::shared_ptr&lt;type&gt;(std::move(ptr)); 更容易捕捉,如果确实有必要,也可以解决。)

我错过了什么吗?

【问题讨论】:

    标签: c++ pointers c++11 shared-ptr


    【解决方案1】:

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

    因此,您提议在没有显着安全收益的情况下创建更多限制,这很可能是它一开始不在标准中的原因。

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

    【讨论】:

    • 指针很容易复制,但如果T*&amp;&amp; 是唯一的构造函数,则不能复制到shared_ptrT* constT*&amp; 都将被删除,所以我不明白你关于“类型不匹配”的观点。 (构造函数中也不会发生模板推导。)我不是质疑make_shared的使用,相反,这会鼓励使用它。
    • 最后,我不明白你关于函数调用抛出异常的场景的观点,这似乎是一个单独的问题,也许需要单独的解决方案,比如制作@987654333的构造函数@private 并强制好友函数 make_shared 成为创建 shared_ptr 的唯一方法。
    • @alfC 你可以有X* a = new X; X* b = a; shared_ptr&lt;X&gt;(a); shared_ptr&lt;X&gt;(b) 这是一个指针复制的例子。或者你可以有X* a = new X; f(a); shared_ptr&lt;X&gt;(a);f(X* a_copy) { shared_ptr&lt;X&gt;(a_copy); }。即使我们限制为 T&& 构造函数,也只有上面的代码会像以前一样编译并失败。
    • @alfC 用于类型不匹配。如果您删除这些构造函数,您可能会遇到这样的情况:如果您拥有它们,模板将大大简化,或者它们没有正确编写(所有类型推导和剥离),因此它们会生成需要删除的构造函数并产生错误的代码。这一点不是关于安全性,而是关于易用性。
    • 据我所知,标准已经更新为评估顺序。虽然 order 仍然是实现定义的,但编译器现在必须完全评估每个参数,因此编译器不能再调用 new int、抛出然后调用 shared_ptr 构造函数。因此,我认为void f(shared_ptr&lt;int&gt; a, shared_ptr&lt;int&gt; b) { }; f(new int(1), new int(2));在发生异常时不会再泄漏。
    【解决方案2】:

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

    shared_ptr 在 C++11 中与右值引用同时引入。 shared_ptr 已经在 boost 中有一个工作参考实现,因此预计它会相对较快地添加到标准库中。

    如果shared_ptr 的构造函数仅支持从右值引用进行构造,则在编译器实现对右值引用的支持之前,它将无法使用。

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

    此外,我认为标准委员会对没有参考实现的 API 进行标准化会感到不舒服,甚至在标准发布之前都无法获得。

    【讨论】:

      【解决方案3】:

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

      struct A {
        private:
           A();
      };
      
      A* A_factory();
      

      在这种情况下,如果您想将从A_factory() 获得的A* 粘贴到shared_ptr&lt;&gt; 中,则必须使用采用原始指针而不是make_shared() 的构造函数。

      在我的脑海中,还有一些其他的例子:

      • 您希望使用 posix_memalign() 为您的类型获取对齐的内存,然后将其存储在带有调用 free() 的自定义删除器的 shared_ptr&lt;&gt; 中(当我们向语言添加对齐分配时,此用例将很快消失!)。
      • 您希望使用调用 munmap() 的自定义删除器将指向使用 mmap() 创建的内存映射区域的指针粘贴到 shared_ptr&lt;&gt; 中(当我们获得 shmem 的标准化工具时,此用例将消失,我希望在接下来的几个月里完成一些工作)。
      • 您希望使用自定义删除器将分配的指针粘贴到 shared_ptr&lt;&gt; 中。

      【讨论】:

      • 我赞成工厂。我最初的问题是关于 shared_ptr 的构造函数,所以它只接受来自工厂、make_shared 或其他方式的输入。
      猜你喜欢
      • 2021-11-15
      • 2021-07-15
      • 2021-11-05
      • 1970-01-01
      • 1970-01-01
      • 2020-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多