Weak_Ptr, Make_Shared and Memory Deallocation

weak_ptr, make_shared and memory deallocation

Is my understanding correct?

Yes. If your weak_ptrs significantly outlive the (large) object and you are tight on memory, it may be beneficial to avoid make_shared.

However, "large" here is measured by sizeof, and many conceptually "large" objects (for example, most standard containers, except std::array) are quite small by that metric, because they allocate additional memory to store their contents, which will be freed as soon as the object is destroyed.

How std::shared_ptr is deallocated?

(Had to edit the answer since I have not read the question properly).

Yes, the memory itself will be around in your snippet, since you have allocated a single block for both control block and the object via make_shared call.

std::make_shared(), std::weak_ptr and cyclic references

It is destroyed. That's one of the reason why weak_ptr exists.

When a is destroyed, the reference counter becomes 0, so the object is destroyed. That means the destructor of the object is called, which destroys a->parent too.

Don't confuse destruction with deallocation. When reference counter becomes 0, or no shared_ptr owns the object, the object is destroyed. If there is any weak_ptr which points the control block, the memory won't be deallocated - because the object was allocated with std::make_shared - but the object is definitely destroyed.

std::weak_ptr assignment with std::make_shared

The weak_ptr can only be dereferenced after locking if a shared_ptr object still exists that's pointing to the same underlying object.

In your first part

std::shared_ptr<int> shared {std::make_shared<int>(42)};
weak = shared;
std::cout << "Meaning of life: " << *weak.lock() << std::endl;

this is indeed the case. In the second part

weak = std::make_shared<int>(23);
std::cout << "Meaning of life: " << *weak.lock() << std::endl;

it is not the case, as the shared_ptr was a temporary object.

What you've encountered here is exactly what weak_ptr was built for - that it be valid only as long as some other shared_ptr points to the same underlying object. That is its purpose:

std::weak_ptr is a smart pointer that holds a non-owning ("weak") reference to an object that is managed by std::shared_ptr... If the original std::shared_ptr is destroyed at this time, the object's lifetime is extended until the temporary std::shared_ptr is destroyed as well.

Do std::weak_ptrs affect when the memory allocated by std::make_shared is deallocated?

If you use make_shared and if the implementation uses a single allocation for both the object and the reference counts, then that allocation cannot be freed until all references (both strong and weak) have been released.

However, the object will be destroyed after all strong references have been released (regardless of whether there are still weak references).

Why does shared_ptr needs to hold reference counting for weak_ptr?

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?

std::shared_ptr, std::weak_ptr and control block

This is a tiny edge issue that gets blown way out of proportion. The only case where this is a problem is if the object is large (relative to available memory), the size is in the base size of the object (not memory that the destructor (of the object or of any of its members) can free), and a weak pointer is likely to significantly outlive the object. This is a rare combination of cases and is almost never significant.

In what sense does a weak_ptr 'own' a shared_ptr?

The control block keeps track of all the weak_ptr references as well as the shared_ptr references. After all, the weak_ptr needs to look somewhere to see if the object is still valid.

Hence, the control block cannot be de-allocated until all shared_ptrs and all weak_ptrs have been destroyed. If you use make_shared the control block and the object are allocated together, which is mostly an optimization, except if the object is outlived by any weak_ptrs.

C++11: How is object deleted if it was constructed using make_shared

With make_shared and allocate_shared, there's only one single reference control block that contains the object itself. It looks something like this:

struct internal_memory_type
{
unsigned char[sizeof T] buf; // make sure the object is at the top for
// efficient dereferencing!
// book keeping data
} internal_memory;

The object is constucted in-place: ::new (internal_memory.buf) T(args...).

Memory for the entire block is allocated with ::operator new, or in case of allocate_shared with the allocator's allocate() function.

When the object is no longer needed, the destructor is called on the object itself, some thing like internal_memory.buf->~T();. When the reference control block is no longer needed, i.e. when all the weak references have disappeared as well as all the strong ones, the reference control block as a whole is freed with ::operator delete, or with the allocator's deallocate() function for allocate_shared.

What Happens to a weak_ptr when Its shared_ptr is Destroyed?

A std::shared_ptr is created using two pieces of memory:

  • A resource block: This holds the pointer to the actual underlying data, e.g. 'int*'

  • A control block: This holds information specific to a shared_ptr, for example reference counts.

(Sometimes these are allocated in a single chunk of memory for efficiency, see std::make_shared)

The control block also stores reference counts for weak_ptr. It will not be deallocated until the last weak_ptr goes out of scope (the weak pointer reference count drops to zero).

So a weak_ptr will know that it's expired because it has access to this control block, and it can check to see what the reference count is for a shared_ptr



Related Topics



Leave a reply



Submit