Shared Pointer: Understanding shared_ptr

Share the Article


The shared pointer manages a data or resource for its entire lifetime. Such management happens through contract of shared ownership between all the shared_ptr objects. No specific shared_ptr (shared pointer) owns the complete object. Instead, all shared pointers pointing to it coordinate among themselves. They all together ensure that destruction of resource shall happen when none of them any longer needs it. It means that when the last shared pointer stops pointing to the object then the compiler deletes such object.

Reference Count:

The shared pointers tracks the shared ownership through a reference count property. This count tracks the count of shared pointers currently pointing to shared data object. When the value of reference count reaches zero, at that point the underlying object becomes ready for destruction. The reference count causes the following implications:

  1. shared_ptr uses more memory to store the reference count value, therefore size of shared_ptr is almost twice more than a simple pointer
  2. The allocation and deallocation of extra memory causes some overhead
  3. The reference count operations must be atomic, so shared_ptr internally uses thread-safety mechanisms. this also causes some overhead.

Header file to be included:

#include <memory> //header for smart pointers

To initialize a shared_ptr, any of the following methods can be used:

using new operator (from C++11)
std::shared_ptr<T> sh1 (new T (constructor arguments));
Using make_shared function (from C++14)
std::shared_ptr<T> sh1 = std::make_shared<T> (constructor arguments); auto sh1 = std::make_shared<T> (constructor arguments);

Shared ownership of data

Following example demonstrate that how three shared pointers are referencing to same data. The memory address of data being pointed by these 3 shared pointers will be same.

#include <iostream> //main header #include <memory> //for smart pointers using namespace std;//namespace int main() { std::shared_ptr<int> sh1 (new int); //First shared_ptr to int resource std::shared_ptr<int> sh2 = shp1; //Second shared_ptr to same int auto sh3 = sh1; //Third shared_ptr auto sh4 = std::make_shared<int>(); //New shared_ptr to a new int cout << sh1.get() << endl; //Prints address cout << sh2.get() << endl; cout << sh3.get() << endl; cout << sh4.get() << endl; return 0; }

The output is

0x1c66c20 0x1c66c20 0x1c66c20 0x1c66c70

How to check the reference count of shared pointers:

The reference count can be printed by using the function “use_count()”

cout << sh1.use_count() << endl; cout << sh2.use_count() << endl;

How shared_ptr can give-up the ownership, any pointer can call reset.

The reset() function will reset the reference count of shared object

sh1.reset(); sh2.reset();

Custom Deleter of shared_ptr

Unlike design of unique_ptr, the custom deleter is not part of the shared_ptr object. However, the customer deleter is stored in a special control block internally. Therefore, specification of custom deleter do not change the size of the shared_ptr.

Due to this flexible design, two shared_pointer pointing to same type of data but having different types of custom deletes are considered as of same type.

#include <iostream> //main header #include <memory> //for smart pointers using namespace std;//namespace class MFWidget { }; int main() { auto customdeleter1 = [](MFWidget *data1) { cout << "customdeleter1 called" << endl; delete data1; }; auto customdeleter2 = [](MFWidget *data2) { cout << "customdeleter2 called" << endl; delete data2; }; std::shared_ptr<MFWidget> sh1(new Widget, customdeleter1); std::shared_ptr<MFWidget> sh2(new Widget, customdeleter2); return 0; }

The output is:

customdeleter2 called customdeleter1 called

The compiler treats both sh1 and sh2 of same type

std::vector<std::shared_ptr<MFWidget>> vpw{ sh1, sh2 };

Internal structure of shared_ptr

The shared_ptr internally has pointer to a control block in addition to actual data object

The control block contains all the meta information related to shared ownership. Like, firstly this is responsible for storing the reference count value. Secondly, the control block may stores a custom deleter. Additionally, it may also store a copy of custom allocator, if the this was specified. Depending on implementation, the control block may also contain additional meta data.

An object’s control block is created when the first shared pointer owns the newly created object

Whenever program uses make_shared function, it results in better efficiency than in raw method to create the shared pointer. This is because inside the control block, the meta information is contained in more optimized way.

Cyclic reference problem with shared_ptr<T>

The shared_ptr manages objects and compiler never destroys them  unless the reference count becomes zero. This causes problem in specific scenario when 2 shared pointers are pointing in a cyclic manner. In such case, the underlying data will never get destroyed.

Here, sp1 and sp2 are two shared pointers. Both of them point to two objects of class A inside each other.

Class A internally has another shared pointer which point in cyclic way between 2 objects.

Now, compiler will not delete the object pointed by sp1 when sp1 goes out of scope. This is because the reference count is not zero. Similarly, the compiler will not delete the object sp2 for the same reason.

#include <iostream> //main header #include <memory> //for smart pointers using namespace std;//namespace class MainFunda { std::shared_ptr<MainFunda> internal_sh; public: MainFunda() { cout << "constructor called" << endl; } void set_sp(std::shared_ptr<MainFunda> arg) { internal_sh = arg; } ~MainFunda() { cout << "Destructor called" << endl; } }; int main() { std::shared_ptr<MainFunda> sh1 (new MainFunda); std::shared_ptr<MainFunda> sh2 (new MainFunda); sh1->set_sp(sh2); sh2->set_sp(sh1); return 0; }

The output is:

constructor called constructor called


To solve this problem the internal shared_ptr<T> should be converted to weak_ptr<T>.

weak_ptr will not lead to increment in reference count but they will share the data just like shared_ptr.

The weak_ptr setup is similar to the shared_ptr. Due to this, the objects of weak_ptr have the same size as shared_ptr objects. Internally, they make use of the same control blocks as shared pointers (Basically, they refer to the weak count instead). Week pointers don’t contribute to shared ownership and therefore, they do not modify the reference count value.

Main Funda: shared_ptr jointly point to a common data and managing such references with the help of a control block

Related Topics:

Parametrized constructor
Virtual Destructor & Pure Virtual Destructor
Smart Pointers: unique_ptr<T>
Diamond problem – Overhead of virtual base
Multiple Inheritance has multiple this pointers
Understanding multiple inheritance & virtual base classes
Understanding the copy constructor
What is move constructor ?
Understanding the order of calling constructors and destructors
What is an explicit constructor ?
Smart Pointers: shared_ptr <T> 
What happens when exception thrown from a constructor?
Why a destructor should never throw exception?
Compiler Generated Destructor is always non-virtual
Which member functions are generated by compiler in class?
Understanding array version of new[] & delete[]

Share the Article

Leave a Reply

Your email address will not be published. Required fields are marked *