是shared_ptr的raw-pointer构造函数是一个错误?

Arv*_*vid 8 c++ shared-ptr make-shared c++11

事后看来,给定的是make_shared,如果shared_ptr用C++ 11引入它,会有一个带有原始指针的构造函数吗?

有没有强大的论据或用例支持这个构造函数?

这本来是可以避免的有据可查的陷阱异常安全内存分配/性能优势利用make_shared.

我认为需要shared_ptr构建的另一个优点make_shared是它可能是引擎盖下的单个指针,降低了它的内存使用并使atomic_compare_exchange之类的东西变得更简单(并且可能更高效).(请参阅C++中的演示文稿)

编辑

我知道一个shared_ptr基本上是一个intrusive_ptr(对象和控制块合并)会缺少当前std :: shared_ptr所具有的功能.喜欢:

  1. 将对象与控制块分开释放的能力(如果你长期生活在weak_ptrs中,这很好)

  2. 与提供原始指针的库的兼容性以及释放它们的责任

  3. 使用自定义删除程序保留任意资源的能力(或者对于非拥有指针没有删除程序)

  4. 能够在保持父对象存活的同时指向子对象(例如成员).

我建议的是,这些功能可能不常用(或者在使用它作为raii-wrapper的情况下)可能不是最合适的,以保证额外的费用:

  1. 一个指向控制块的单独指针
  2. (可能)更复杂的atomic_compare_exchange逻辑,可能不值得.

在C++ 98世界中(引入了shared_ptr),make_shared不太实用且用户友好性较差(缺乏完美的转发需要参考包装器和缺少可变参数模板使得实现笨重).

Bar*_*rry 14

事后看来,给定的是make_shared,如果shared_ptr用C++ 11引入它,会有一个带有原始指针的构造函数吗?

如果你不控制对象的分配怎么办?如果您需要使用自定义删除器怎么办?如果你需要列表初始化而不是parens怎么办?

这些案件都不是由处理的make_shared.

另外,如果你正在使用weak_ptr,shared_ptr分配的通道make_shared也不会释放任何内存,直到所有weak_ptrs都被销毁.因此,即使你有一个普通的共享指针,上面没有一个适用,你可能仍然喜欢原始指针构造函数.

另一种情况是,如果你的类型为operator new和提供重载operator delete.这些可能使它不适合make_shared,因为那些超载不会被调用 - 并且可能它们存在是有原因的.

  • @Martin当然可以,但是说你应该更喜欢`make_shared'并且暗示替代方案是设计缺陷之间存在很大差异. (3认同)
  • 我建议OP阅读Scott Myer的Effective Modern C++,以全面描述设计的基本原理. (2认同)
  • @Arvid不,我不是说(也不是真的 - 它不是存储的两倍,而且引用计数逻辑是相同的 - 它们只是在不同的时间以不同的方式释放内存).有些情况下`make_shared`不是一个选项 - 这不是与成本相关的参数. (2认同)
  • @Arvid好的,当然.如果只有`make_shared()`-able,那么`shared_ptr`几乎不会有用.但是`make_shared`是一个优化而不是One True Constructor - 因此使用成本会使得推理倒退.当然,我们所付出的"成本"正在使优化效果不尽如人意 - 作为使课程更有用的回报. (2认同)

Nic*_*las 13

你的逻辑的问题是相信,为什么shared_ptr管理指针和get指针之间的区别是因为make_shared不可用.因此,如果我们强迫每个人都用来make_shared创造shared_ptr,我们就不需要这种区别.

这是不正确的.

你可以实现shared_ptr没有这种区别的基于指针的构造函数.毕竟,在初始创建托管时shared_ptr,get指针和托管指针是相同的.如果你想shared_ptr成为sizeof(T*),你可以从托管块中shared_ptr获取get指针.无论是否T嵌入在托管块中,都是如此.

因此,这种区别实际上没有任何关系,make_shared并且它能够将其嵌入到T与托管块相同的内存中.或者更确切地说,缺乏它.

不,托管指针和之间的区别get是创建的指针,因为它增加功能shared_ptr.重要的.你列出了其中一些,但你错过了其他人:

  • 具有shared_ptr基类的能力.那是:

    shared_ptr<base> p = make_shared<derived>(...);
    
    Run Code Online (Sandbox Code Playgroud)

    为此,您必须区分特定实例指向的内容与控制块控制的内容.

  • static_pointer_castdynamic_pointer_cast(以及reinterpret_pointer_cast在C++ 17中).这些都依赖于托管指针和get指针之间的区别.

    • 这也包括enable_shared_from_this在基类中.
  • A shared_ptr指向一个类型的成员子对象,该对象本身由a管理shared_ptr.同样,它要求托管指针与get指针不同.

您似乎也忽略了管理不是由您创建的指针的能力.这是一项关键能力,因为它允许您与其他代码库兼容.在内部,您可以shared_ptr用来管理1998年编写的库所做的事情.

按照自己的方式,将代码划分为两个时代:pre-C++ 11和post-C++ 11.shared_ptr对于没有为C++ 11明确编写的代码,您将不做任何事情.

将所有这些功能包装成单一类型的事情是这样的:

你不需要另一个.

shared_ptr,因为它满足了这么多的需求,几乎可以在任何地方有效地使用.它可能不是绝对,最高效型可能,但做的工作在几乎所有情况下.这样做并不是很慢.

它使用多态来处理共享所有权.它处理成员对象的共享所有权.它处理您未分配的内存的共享所有权.它处理具有特殊分配/释放需求的内存共享所有权.等等.

如果您需要共享所有权语义,并且需要它才能工作,那么shared_ptr每次都会让您回头.根据您提出的建议,总会有一些限制因素可以帮助您完成工作.

默认情况下,优先使用的类型优先于不支持的类型.


Ric*_*ges 5

std::shared_ptr 不仅仅是在堆上分配对象.

考虑将其用作自动关闭共享文件句柄:

#include <cstdio>
#include <memory>


int main()
{
  auto closer = [](FILE* fp) { std::fclose(fp); };
  auto fp = std::shared_ptr<FILE>(std::fopen("foo.txt", "r"),
                                  closer);
}
Run Code Online (Sandbox Code Playgroud)

  • "它是"是"它是"的缩写.这不是占有形式. (3认同)