cpp make_shared 用于 void 指针

发布于 2024-12-22 22:37:55 字数 134 浏览 2 评论 0原文

我想使用 std::make_shared 创建一个 void 指针。由于make_shared应该比shared_ptr(new T)更快,并且异常保存我想知道是否有一个库函数可以以make_shared方式创建shared_ptr(new foo)。

I would like to use std::make_shared to create a void pointer. Since make_shared is supposed to be faster than shared_ptr(new T), and exception save I wonder if there is a library function to create a shared_ptr(new foo) in the make_shared way.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

温折酒 2024-12-29 22:37:55

您可以将任何 shared_ptr 转换为 shared_ptr,而不会损失与 make_shared 相关的效率:

#include <memory>

struct foo {};

int main()
{
    std::shared_ptr<void> p = std::make_shared<foo>();
}

转换会保留 foo 和同一内存分配中的引用计数,即使您现在通过 void* 引用它。

更新

这是如何工作的?

std::shared_ptr 的一般结构是两个指针:

                          +------> foo
                          |         ^
p1  ---------> (refcount, +)        |
p2  --- foo* -----------------------+

p1 指向一个包含引用计数的控制块(实际上是两个引用计数:一个用于强所有者一个用于弱所有者)、一个删除器、一个分配器和一个指向对象“动态”类型的指针。 “动态”类型是 shared_ptr 构造函数看到的对象类型,例如 Y (它可能与 相同,也可能不同) T)。

p2 的类型为 T*,其中 Tshared_ptr< 中的 T 相同。 /代码>。将此视为存储对象的“静态”类型。当您取消引用 shared_ptr 时,取消引用的是 p2。当您销毁 shared_ptr 时,如果引用计数变为零,则控制块中的指针将有助于销毁 foo

在上图中,控制块和 foo 都是动态分配的。 p1是一个拥有指针,控制块中的指针是一个拥有指针。 p2 是一个非拥有指针。 p2only 函数是取消引用(箭头运算符、get() 等)。

当您使用 make_shared() 时,实现有机会将 foo 与引用计数和其他数据一起放在控制块中

p1  ---------> (refcount, foo)
p2  --- foo* --------------^

:现在只有一个分配:现在嵌入 foo 的控制块。

当上面的内容转换为 shared_ptr 时,会发生以下情况:

p1  ---------> (refcount, foo)
p2  --- void* -------------^

p2 的类型从 foo* 更改为 无效*。就是这样。 (除了增加/减少引用计数以考虑临时对象的复制和销毁之外,这可以通过从右值构造来消除)。当引用计数变为零时,它仍然是销毁通过 p1 找到的 foo 的控制块。 p2不参与销毁操作。

p1 实际上指向控制块的通用基类。该基类不知道派生控制块中存储的类型foo。当实际对象类型Y已知时,派生控制块在shared_ptr的构造函数中构造。但从那时起,shared_ptr 只能通过 control_block_base* 与控制块通信。因此,诸如破坏之类的事情是通过虚拟函数调用发生的。

C++11 中从右值 shared_ptr 进行 shared_ptr 的“移动构造”只需复制两个内部指针,而不需要复制两个内部指针。来操纵引用计数。这是因为右值 shared_ptr 无论如何都会消失:

// shared_ptr<foo> constructed and destructed within this statement
std::shared_ptr<void> p = std::make_shared<foo>();

这可以在 shared_ptr 构造函数源代码中最清楚地看到:

template<class _Tp>
template<class _Yp>
inline _LIBCPP_INLINE_VISIBILITY
shared_ptr<_Tp>::shared_ptr(shared_ptr<_Yp>&& __r,
                            typename enable_if<is_convertible<_Yp*, _Tp*>::value, __nat>::type)
         _NOEXCEPT
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_)
{
    __r.__ptr_ = 0;
    __r.__cntrl_ = 0;
}

在转换构造之前,引用计数仅为 1。在转换构造之后,引用计数仍然为 1,源在其析构函数运行之前指向任何内容。简而言之,这就是移动语义的乐趣! :-)

You can convert any shared_ptr<foo> to shared_ptr<void> without the loss of efficiency associated with make_shared:

#include <memory>

struct foo {};

int main()
{
    std::shared_ptr<void> p = std::make_shared<foo>();
}

The conversion keeps the foo and the reference count in the same memory allocation, even though you now refer to it via a void*.

Update

How does this work?

The general structure of a std::shared_ptr<foo> is two pointers:

                          +------> foo
                          |         ^
p1  ---------> (refcount, +)        |
p2  --- foo* -----------------------+

p1 points to a control block containing a reference count (actually two reference counts: one for strong owners and one for weak owners), a deleter, an allocator, and a pointer to the "dynamic" type of the object. The "dynamic" type is the type of the object that the shared_ptr<T> constructor saw, say Y (which may or may not be the same as a T).

p2 has type T* where the T is the same T as in shared_ptr<T>. Think of this as the "static" type of the stored object. When you dereference a shared_ptr<T>, it is p2 that gets dereferenced. When you destruct a shared_ptr<T>, and if the reference count goes to zero, it is the pointer in the control block that aids in the destruction of foo.

In the above diagram, both the control block and the foo are dynamically allocated. p1 is an owning pointer, and the pointer in the control block is an owning pointer. p2 is a non-owning pointer. p2's only function is dereference (arrow operator, get(), etc.).

When you use make_shared<foo>(), the implementation has the opportunity to put the foo right in the control block, alongside of the reference counts and other data:

p1  ---------> (refcount, foo)
p2  --- foo* --------------^

The optimization here is that there is now only a single allocation: the control block which now embeds the foo.

When the above gets converted to a shared_ptr<void>, all that happens is:

p1  ---------> (refcount, foo)
p2  --- void* -------------^

I.e. The type of p2 changes from foo* to void*. That's it. (besides incrementing/decrementing reference counts to account for a copy and destruction of a temporary -- which can be elided by construction from an rvalue). When the reference count goes to zero, it is still the control block that destroys the foo, found via p1. p2 does not participate in the destruction operation.

p1 actually points to a generic base class of the control block. This base class is ignorant of the type foo stored in the derived control block. The derived control block is constructed in shared_ptr's constructor at the time the actual object type Y is known. But from then on the shared_ptr can only communicate with the control block via a control_block_base*. So things like destruction happen via a virtual function call.

The "move construction" of a shared_ptr<void> from an rvalue shared_ptr<foo> in C++11 merely has to copy the two internal pointers, and does not have to manipulate the reference count. This is because the rvalue shared_ptr<foo> is about to go away anyway:

// shared_ptr<foo> constructed and destructed within this statement
std::shared_ptr<void> p = std::make_shared<foo>();

This can be seen most plainly in the shared_ptr constructor source code:

template<class _Tp>
template<class _Yp>
inline _LIBCPP_INLINE_VISIBILITY
shared_ptr<_Tp>::shared_ptr(shared_ptr<_Yp>&& __r,
                            typename enable_if<is_convertible<_Yp*, _Tp*>::value, __nat>::type)
         _NOEXCEPT
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_)
{
    __r.__ptr_ = 0;
    __r.__cntrl_ = 0;
}

Before the converting construction the reference count is only 1. And after the converting construction the reference count is still 1, with the source pointing to nothing just prior to its destructor running. This, in a nutshell, is the joy of move semantics! :-)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文