Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::shared_ptr internals, weak count more than expected

In one episode (35:00) of Advanced STL series, Stephan T Lavavej showed that _Weaks, the counter whose value of 0 determines when to delete the _Ref_count structure, equals the number of alive weak_ptr, plus 1 if there are alive shared_ptrs. He explained that it is necessary because of thread safety: if _Weaks only equaled the number of weak_ptr then when last weak_ptr would go out of scope it would be also necessary to check _Uses, the counter of alive shared_ptrs, to check if _Ref_count can be deleted. And this is unacceptable because of lack of atomicity.

Assuming that _Uses = number of alive shared_ptrs, _Weaks = number of alive weak_ptrs, imagine we have the following scenario:

  • (_Uses = 0, _Weaks = 1): last weak_ptr goes out of scope, decrement _Weaks

  • (_Uses = 0, _Weaks = 0): if _Uses is equal to 0, delete _Ref_count structure

What, in multithreaded application, can go wrong, which forces us to use _Weak = number of alive weak_ptr + (number of shared_ptr ? 1 : 0) implementation?

like image 862
user2551229 Avatar asked Apr 08 '17 16:04

user2551229


People also ask

Is shared_ptr slow?

shared_ptr are noticeably slower than raw pointers. That's why they should only be used if you actually need shared ownership semantics. Otherwise, there are several other smart pointer types available. scoped_ptr and auto_ptr (C++03) or unique_ptr (C++0x) both have their uses.

What happens when shared_ptr goes out of scope?

The smart pointer has an internal counter which is decreased each time that a std::shared_ptr , pointing to the same resource, goes out of scope – this technique is called reference counting. When the last shared pointer is destroyed, the counter goes to zero, and the memory is deallocated.

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.

What happens when you move a shared_ptr?

By moving the shared_ptr instead of copying it, we "steal" the atomic reference count and we nullify the other shared_ptr . "stealing" the reference count is not atomic, and it is hundred times faster than copying the shared_ptr (and causing atomic reference increment or decrement).

What is the difference between shared_ptr and weak_ptr?

When a weak_ptr is created from a shared_ptr, it refers to the same control block but does not share the ownership of the managed object. It is not possible to directly access the managed object through a weak_ptr.

What is shared_ptr in C++11?

The C++11 std::shared_ptr<T> is a shared ownership smart pointer type. Several shared_ptr instances can share the management of an object's lifetime through a common control block. The managed object is deleted when the last owning shared_ptr is destroyed (or is made to point to another object).

How many pointers does a shared_ptr have?

In a typical implementation, a shared_ptr contains only two pointers: a raw pointer to the managed object that is returned by get (), and a pointer to the control block. A shared_ptr control block at least includes a pointer to the managed object or the object itself, a reference counter, and a weak counter.

What happens to the reference count when a shared_ptr is destroyed?

The reference count increases as a new shared_ptr is constructed, and it decreases as an owning shared_ptr is destroyed. One exception to that is the reference count is left unchanged when a shared_ptr is moved because the move-constructor transfers the ownership from the source to the newly constructed shared_ptr.


1 Answers

Imagine the following scenario. Thread A holds a shared_ptr, thread B holds a corresponding weak_ptr. So in your implementation, we have _Uses == 1 and _Weaks == 1.

There are two possible implementations for the smart pointer destructors, both with problems.

Implementation 1: decrement, then check

B's weak_ptr is destroyed. Decrements _Weaks. We have _Uses == 1, _Weaks == 0. B prepares to check _Uses, but ...

Context switch.

A's shared_ptr is destroyed. Decrements _Uses. We have _Uses == 0, _Weaks == 0. Begin destruction of _Ref_count.

Context switch.

B now get round to checking _Uses. It's 0. Begin destruction of _Ref_count.

Both threads are now in the process of destroying _Ref_count. Not good.

Implementation 2: check, then decrement

B's weak_ptr is destroyed. Check _Uses. It's 1, no destruction will happen. B prepares to decrement _Weaks, but ...

Contex switch.

A's shared_ptr is destroyed. Checks _Weaks. It's 1, no destruction will happen. Decrement _Uses. We have _Uses == 0, _Weaks == 1. Done.

Context switch.

B now gets round to decrement _Weaks. We have _Uses == 0, _Weaks == 0. Nothing else to do.

We have leaked the _Ref_count. Not good.

like image 113
Angew is no longer proud of SO Avatar answered Oct 06 '22 16:10

Angew is no longer proud of SO