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

时间:2016-05-22 17:45:25

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

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

是否存在支持此构造函数的强大论据或用例?

它可以避免使用make_shared exception-safety和使用shared_ptr memory allocation/performance advantage的错误记录陷阱。

我认为通过make_shared进行const BitsPerByte = 8; BitsPerInteger = SizeOf(Integer) * BitsPerByte; type TBitRange = Byte; function SetBit(const Value: Integer; const Bit: TBitRange): Integer; begin Result := Value or (1 shl (Bit mod BitsPerInteger)); end; 构建的另一个好处是,它可能是引擎盖下的单个指针,降低了它的内存使用并使atomic_compare_exchange之类的东西变得更加简单(可能更有效率)。 (见presentation from C++Now

修改

我理解一个shared_ptr,它基本上是一个intrusive_ptr(对象和控制块合并)会缺少当前std :: shared_ptr所具有的功能。像:

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

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

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

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

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

    1. 指向控制块的单独指针
    2. (可能)更复杂的atomic_compare_exchange逻辑,可能不值得。
    3. 在C ++ 98世界中(引入了shared_ptr),make_shared不太实用且用户友好性较差(缺乏完美的转发需要引用包装器和缺少可变参数模板使得实现笨重)。

3 个答案:

答案 0 :(得分:14)

  

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

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

这些案件都不是make_shared处理的。

此外,如果您正在使用weak_ptr,则shared_ptr通过make_shared分配的weak_ptr不会释放任何内存,直到所有operator new被销毁为止同样。因此,即使你有一个普通的共享指针,上面没有一个适用,你可能仍然更喜欢原始指针构造函数。

另一种情况是,如果您的类型为operator deletemake_shared提供了重载。这些可能使它不适合ulimit -t 1,因为那些重载不会被调用 - 并且可能它们存在是有原因的。

答案 1 :(得分:13)

您的逻辑问题是相信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>(...); 的能力。那就是:

    static_pointer_cast

    要做到这一点,必须区分特定实例指向的内容和控制块控制的内容。

  • dynamic_pointer_castreinterpret_pointer_cast(以及C ++中的get 17)。这些都依赖于托管指针和enable_shared_from_this指针之间的区别。

    • 这还包括基类中的shared_ptr
  • 一个shared_ptr,指向一个由get管理的类型的成员子对象。同样,它要求托管指针与shared_ptr指针不相同。

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

按照自己的方式,将代码划分为两个时代:pre-C ++ 11和post-C ++ 11。对于未明确为C ++ 11编写的代码,shared_ptr将不执行任何操作。

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

你不需要另一个。

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

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

如果您需要共享所有权语义,并且需要工作,{{1}}每次都会回来。根据您提出的建议,总会有一些限制,这些都会妨碍您完成工作。

默认情况下,首选应该首选的类型不是。

答案 2 :(得分: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);
}