shared_ptr 的原始指针构造函数是错误的吗?
Was the raw-pointer constructor of shared_ptr a mistake?
事后看来,给定 make_shared
,如果 C++11 引入了原始指针,shared_ptr
是否会有一个采用原始指针的构造函数?
是否有支持此构造函数的有力论据或用例?
它可以避免使用 make_shared
.
的 exception-safety and the memory allocation/performance advantage 的有据可查的陷阱
我相信通过 make_shared
要求 shared_ptr
构造的另一个优点是它可以是引擎盖下的单个指针,降低其内存使用并使之成为 atomic_compare_exchange a lot simpler (and possibly more efficient). (see presentation from C++Now)
我知道 shared_ptr 基本上是 intrusive_ptr(对象和控制块合并)会缺少当前 std::shared_ptr 所具有的功能。喜欢:
能够从控制块中单独释放对象(如果你已经长寿了,这很好weak_ptrs)
与提供原始指针的库的兼容性以及释放它们的责任
使用自定义删除器(或无删除器,对于非拥有指针)保存任意资源的能力
能够在使父对象保持活动状态的同时指向子对象(例如,成员)。
我的建议是这些功能可能使用得不够普遍(或者在将其用作 RAII-wrapper 的情况下)可能不是最合适的,以保证额外费用:
- 指向控制块的单独指针
- (可能)更复杂的 atomic_compare_exchange 逻辑,可能不值得。
在 C++98 世界中(shared_ptr 被引入)make_shared 不太实用且用户友好度较低(缺少完美转发需要引用包装器和缺少可变参数模板使实施笨拙)。
In hindsight, given make_shared
, would shared_ptr
have a constructor that takes a raw pointer had it been introduced with C++11?
如果不控制对象的分配怎么办?如果您需要使用自定义删除器怎么办?如果您需要列表初始化而不是括号怎么办?
None 个案例由 make_shared
处理。
此外,如果您正在使用 weak_ptr
,通过 make_shared
分配的 shared_ptr
将不会释放任何内存,直到所有 weak_ptr
也被销毁.因此,即使你有一个普通的共享指针,其中 none 以上的应用,你可能 still 更喜欢原始指针构造函数。
另一种情况是,如果您的类型为 operator new
和 operator delete
提供重载。这些可能使其不适合 make_shared
,因为这些重载不会被调用 - 并且它们的存在可能是有原因的。
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);
}
您的逻辑存在问题,您认为 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
到 class 的能力。即:
shared_ptr<base> p = make_shared<derived>(...);
为此,您必须区分特定实例指向的内容和控制块控制的内容。
static_pointer_cast
和 dynamic_pointer_cast
(以及 C++17 中的 reinterpret_pointer_cast
)。这些都依赖于托管指针和get
指针的区分。
- 这还包括
enable_shared_from_this
基数 classes。
一个 shared_ptr
,它指向一个本身由 shared_ptr
管理的类型的成员子对象。同样,它要求托管指针与 get
指针不同。
您似乎也轻视了管理非您创建的指针的能力。这是一项 关键 能力,因为它允许您与其他代码库兼容。在内部,您可以使用 shared_ptr
来管理由 1998 年编写的库制作的东西。
按照您的方式,您将代码分为两个时期:pre-C++11 和 post-C++11。您的 shared_ptr
不会对任何未明确为 C++11 编写的代码执行任何操作。
将所有这些功能包装成一个类型的事情是这样的:
你不需要另一个。
shared_ptr
,因为它满足了如此多的需求,几乎可以在任何地方有效使用。它可能不是绝对最有效的类型,但 将 几乎在所有情况下都能胜任。而且这样做并不慢。
它通过多态处理共享所有权。它处理成员对象的共享所有权。它处理您未分配的内存的共享所有权。它处理具有特殊 allocation/deallocation 需求的内存共享所有权。等等。
如果您需要共享所有权语义,并且需要它工作,shared_ptr
每次都会支持您。有了你建议的想法,总会有一些限制,你完成工作的方式。
默认情况下,应该优先选择一种有效的类型,而不是无效的类型。
事后看来,给定 make_shared
,如果 C++11 引入了原始指针,shared_ptr
是否会有一个采用原始指针的构造函数?
是否有支持此构造函数的有力论据或用例?
它可以避免使用 make_shared
.
我相信通过 make_shared
要求 shared_ptr
构造的另一个优点是它可以是引擎盖下的单个指针,降低其内存使用并使之成为 atomic_compare_exchange a lot simpler (and possibly more efficient). (see presentation from C++Now)
我知道 shared_ptr 基本上是 intrusive_ptr(对象和控制块合并)会缺少当前 std::shared_ptr 所具有的功能。喜欢:
能够从控制块中单独释放对象(如果你已经长寿了,这很好weak_ptrs)
与提供原始指针的库的兼容性以及释放它们的责任
使用自定义删除器(或无删除器,对于非拥有指针)保存任意资源的能力
能够在使父对象保持活动状态的同时指向子对象(例如,成员)。
我的建议是这些功能可能使用得不够普遍(或者在将其用作 RAII-wrapper 的情况下)可能不是最合适的,以保证额外费用:
- 指向控制块的单独指针
- (可能)更复杂的 atomic_compare_exchange 逻辑,可能不值得。
在 C++98 世界中(shared_ptr 被引入)make_shared 不太实用且用户友好度较低(缺少完美转发需要引用包装器和缺少可变参数模板使实施笨拙)。
In hindsight, given
make_shared
, wouldshared_ptr
have a constructor that takes a raw pointer had it been introduced with C++11?
如果不控制对象的分配怎么办?如果您需要使用自定义删除器怎么办?如果您需要列表初始化而不是括号怎么办?
None 个案例由 make_shared
处理。
此外,如果您正在使用 weak_ptr
,通过 make_shared
分配的 shared_ptr
将不会释放任何内存,直到所有 weak_ptr
也被销毁.因此,即使你有一个普通的共享指针,其中 none 以上的应用,你可能 still 更喜欢原始指针构造函数。
另一种情况是,如果您的类型为 operator new
和 operator delete
提供重载。这些可能使其不适合 make_shared
,因为这些重载不会被调用 - 并且它们的存在可能是有原因的。
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);
}
您的逻辑存在问题,您认为 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
到 class 的能力。即:shared_ptr<base> p = make_shared<derived>(...);
为此,您必须区分特定实例指向的内容和控制块控制的内容。
static_pointer_cast
和dynamic_pointer_cast
(以及 C++17 中的reinterpret_pointer_cast
)。这些都依赖于托管指针和get
指针的区分。- 这还包括
enable_shared_from_this
基数 classes。
- 这还包括
一个
shared_ptr
,它指向一个本身由shared_ptr
管理的类型的成员子对象。同样,它要求托管指针与get
指针不同。
您似乎也轻视了管理非您创建的指针的能力。这是一项 关键 能力,因为它允许您与其他代码库兼容。在内部,您可以使用 shared_ptr
来管理由 1998 年编写的库制作的东西。
按照您的方式,您将代码分为两个时期:pre-C++11 和 post-C++11。您的 shared_ptr
不会对任何未明确为 C++11 编写的代码执行任何操作。
将所有这些功能包装成一个类型的事情是这样的:
你不需要另一个。
shared_ptr
,因为它满足了如此多的需求,几乎可以在任何地方有效使用。它可能不是绝对最有效的类型,但 将 几乎在所有情况下都能胜任。而且这样做并不慢。
它通过多态处理共享所有权。它处理成员对象的共享所有权。它处理您未分配的内存的共享所有权。它处理具有特殊 allocation/deallocation 需求的内存共享所有权。等等。
如果您需要共享所有权语义,并且需要它工作,shared_ptr
每次都会支持您。有了你建议的想法,总会有一些限制,你完成工作的方式。
默认情况下,应该优先选择一种有效的类型,而不是无效的类型。