Why does shared_ptr needs to hold reference counting for weak_ptr?

The weak_ptr need to point to something that can tell if the object exist or not so it knows if it can be converted to a shared_ptr. Therefore a small object is needed to housekeep this information.

This housekeeping control block needs to be destroyed when the last week_ptr (or shared_ptr) is removed. Therefore it has to keep count of both the shared_ptr and the week_ptr's.

Note that the housekeeping control block is not the same as the object the ptr's point to and therefore the week_ptr do not affect the objects lifetime.

There is a bunch of different ways to implement smart pointers depending on what behavior you would like it to have. If you want to know more I would recommend "Modern C++ Design" by Alexandrescu (https://www.amazon.com/Modern-Design-Generic-Programming-Patterns/dp/0201704315)


Both weak_ptr and shared_ptr point to memory containing control block. If you delete control block as soon as shared_ptr counter reaches 0 (but weak counter doesn't), you are left with weak_ptrs pointing to garbage memory. Then when you try to use weak_ptr, it reads deallocated memory and bad things happen (UB).

For this reason, control block must be left alive (allocated and constructed, not destroyed nor deallocated) as long as any weak_ptr may try to read it.

Main (pointed-to) object will be destroyed and may (hopefully) be deallocated as soon as shared counter reaches 0. Control block will be destroyed and deallocated when both counters reach 0.


The reference count controls the lifetime of the pointed-to-object. The weak count does not, but does control (or participate in control of) the lifetime of the control block.

If the reference count goes to 0, the object is destroyed, but not necessarily deallocated. When the weak count goes to 0 (or when the reference count goes to 0, if there are no weak_ptrs when that happens), the control block is destroyed and deallocated, and the storage for the object is deallocated if it wasn't already.

The separation between destroying and deallocating the pointed-to-object is an implementation detail you don't need to care about, but it is caused by using make_shared.

If you do

shared_ptr<int> myPtr(new int{10});

you allocate the storage for the int, then pass that into the shared_ptr constructor, which allocates storage for the control block separately. In this case, the storage for the int can be deallocated as early as possible: as soon as the reference count hits 0, even if there is still a weak count.

If you do

auto myPtr = make_shared<int>(10);

then make_shared might perform an optimisation where it allocates the storage for the int and the control block in one go. This means that the storage for the int can't be deallocated until the storage for the control block can also be deallocated. The lifetime of the int ends when the reference count hits 0, but the storage for it is not deallocated until the weak count hits 0.

Is that clear now?