8

事后看来,如果用 C++11 引入了给定的构造函数,它make_sharedshared_ptr接受一个原始指针吗?

是否有支持此构造函数的有力论据或用例?

它将避免有据可查的异常安全陷阱和使用.make_shared

我相信要求的另一个好处shared_ptr我相信要求构建 viamake_shared是它可以是引擎盖下的单个指针,从而降低其内存使用并使atomic_compare_exchange 之类的事情变得更简单(并且可能更有效)。(参见C++Now 的演示文稿

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

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

  2. 与向您提供原始指针并负责释放它们的库的兼容性

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

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

我的建议是,这些功能可能不够常用(或者在将其用作RAII包装器的情况下)可能不是最合适的,以保证额外的成本:

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

C++98世界中(引入了 shared_ptr),make_shared 不太实用且对用户不太友好(缺少完美的转发需要引用包装器,并且缺少可变参数模板使得实现笨拙)。

4

3 回答 3

14

事后看来,如果用 C++11 引入了给定的构造函数,它make_sharedshared_ptr接受一个原始指针吗?

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

这些案件均由make_shared.

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

另一种情况是,如果您的类型为operator newand提供重载operator delete。这些可能使其不适合make_shared,因为不会调用这些重载 - 并且可能它们的存在是有原因的。

于 2016-05-22T18:05:08.943 回答
13

您的逻辑问题在于认为shared_ptr托管指针和指针之间存在区别的原因get是因为make_shared不可用。因此,如果我们强迫每个人都使用make_sharedto create shared_ptr,我们就不需要这种区别了。

这是不正确的。

您可以shared_ptr在没有这种区别的情况下实现 's 基于指针的构造函数。毕竟,在初始创建 managedshared_ptr时,get指针和 managed 指针是一样的。如果您想shared_ptr成为sizeof(T*),您可以从托管块中shared_ptr获取get指针。这与是否T嵌入托管块无关。

因此,区别实际上与将其嵌入与托管块相同的内存中的能力无关。或者更确切地说,缺乏它。make_sharedT

不,托管指针和指针之间的区别get是创建的,因为shared_ptr. 重要的。您列出了其中一些,但您错过了其他一些:

  • 拥有shared_ptr一个基类的能力。那是:

    shared_ptr<base> p = make_shared<derived>(...);
    

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

  • static_pointer_castdynamic_pointer_castreinterpret_pointer_cast在 C++17 中)。这些都依赖于托管指针和get指针之间的区别。

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

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

按照您的方式,您将代码分为两个时期:C++11 之前和 C++11 之后。shared_ptr对于任何未明确为 C++11 编写的代码,您将一事无成。

将所有这些特性包装成一个类型的事情是这样的:

你不需要另一个。

shared_ptr,因为它满足了如此多的需求,几乎可以在任何地方有效地使用。它可能不是绝对最有效的类型,但几乎在所有情况下都能胜任。而且这样做并不慢。

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

如果您需要共享所有权语义,并且需要它来工作,那么shared_ptr每次都会得到您的支持。有了你提出的想法,总会有一些限制,阻碍你完成工作。

默认情况下,应该首选一种有效的类型而不是无效的类型。

于 2016-05-25T22:22:06.340 回答
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);
}
于 2016-05-22T18:18:22.873 回答