Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does shared_ptr use placement new

Tags:

c++

c++11

I've read at many places that when using make_shared<T> to create a shared_ptr<T>, its control block contains a block of storage large enough to hold a T, and then the object is constructed inside the storage with placement new. Something like this:

template<typename T>
struct shared_ptr_control_block {
    std::atomic<long> count;
    std::atomic<long> weak_count;
    std::aligned_storage_t<sizeof (T), alignof (T)> storage;
};

But I'm a bit confused why we can't just have a member variable with type T instead? Why create the raw storage then use placement new? Can't it be combined in one step with a normal object of type T?

like image 971
Zizheng Tai Avatar asked Jan 18 '17 09:01

Zizheng Tai


People also ask

Why would you choose shared_ptr instead of Unique_ptr?

Use unique_ptr when if you want to have single ownership(Exclusive) of resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. Use shared_ptr if you want to share ownership of resource .

Why is shared_ptr unique deprecated?

What is the technical problem with std::shared_ptr::unique() that is the reason for its deprecation in C++17? this function is deprecated as of C++17 because use_count is only an approximation in multi-threaded environment.

What is the purpose of the shared_ptr <> template?

std::shared_ptr is a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr objects may own the same object.

What is difference between make_shared and shared_ptr?

The difference is that std::make_shared performs one heap-allocation, whereas calling the std::shared_ptr constructor performs two.


2 Answers

It's to allow lifetime management.

The control block is not destroyed until weak_count is zero. The storage object is destroyed as soon as count reaches zero. That means you need to directly call the destructor of storage when the count reaches zero, and not in the destructor of the control block.

To prevent the destructor of the control block calling the destructor of storage, the actual type of storage cannot be T.

If we only had the strong reference count, then a T would be fine (and much simpler).


In actual fact, the implementation is a bit more complex than this. Remember that a shared_ptr can be constructed by allocating a T with new, and then constructing the shared_ptr from that. Thus the actual control block looks more like:

template<typename T>
struct shared_ptr_control_block {
    std::atomic<long> count;
    std::atomic<long> weak_count;
    T* ptr;
};

and what make_shared allocates is:

template<typename T>
struct both {
    shared_ptr_control_block cb;
    std::aligned_storage_t<sizeof (T), alignof (T)> storage;
};

And cb.p is set to the address of storage. Allocating the both structure in make_shared means that we get a single memory allocation, rather than two (and memory allocations are expensive).

Note: I have simplified: There has to be a way for the shared_ptr destructor to know whether the control block is part of both (in which case the memory cannot be released until done), or not (in which case it can be freed earlier). This could be a simple bool flag (in which case the control block is bigger), or by using some spare bits in a pointer (which is not portable - but the standard library implementation doesn't have to be portable). The implementation can be even more complex to avoid storing the pointer at all in the make_shared case.

like image 151
Martin Bonner supports Monica Avatar answered Sep 21 '22 08:09

Martin Bonner supports Monica


As weak pointers may outlive the stored object, the lifetime of the control block may have to exceed the lifetime of the stored object. If the managed object would be a member variable it could only get destroyed when the control block gets destroyed (or the destructor would be called two times).

The fact that the storage stays allocated even after the object itself is destructed can actually be a disadvantage of make_shared in memory constraint systems (although I don't know if this is something encountered in practice).

like image 29
MikeMB Avatar answered Sep 19 '22 08:09

MikeMB