Join the Stack Overflow Community
Stack Overflow is a community of 6.6 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

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?

share|improve this question
up vote 9 down vote accepted

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.

share|improve this answer
    
Ah, I see now. So if we had only one reference count (the strong count), then a T would be enough, right? – Zizheng Tai 5 hours ago
    
@ZizhengTai, yes – Andrei R. 5 hours ago
    
It might be worth mentioning that make_shared combines the control block and the object in order to reduce the number of memory allocations required. simply constructing a shared_ptr required one allocation for the object being shared and one allocation for the control block. – Richard Hodges 5 hours ago

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).

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.