cpp make_shared for void pointers

此生再无相见时 提交于 2019-12-01 13:33:22

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! :-)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!