std::shared_ptr

Contents[Show]

std::shared_ptr's share the resource. The shared reference counter counts the number of owners. Copying a std::shared_ptr increases the reference count by one. Destroying a std::shared_ptr decreases the reference count by one. If the reference count becomes zero, the resource will automatically be released. 

 

Before I deal with the details of the std::shared_ptr I will bring you on the same page and therefore explain the basics.

 

The Basics

Copying a std::shared_ptr increases the reference count by one. Both smart pointers use afterwards the same resource. I depicted this scenario.

sharedPtr

Thanks to shared1 shared2 is initialized. At the end the reference count is 2 and both smart pointers have the same resource.

The application

The program shows the typical usage of smart pointers. To get a visual idea of the life cycle of the resource I put a short message in the constructor and destructor of MyInt (line 8 - 16).

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// sharedPtr.cpp

#include <iostream>
#include <memory>

using std::shared_ptr;

struct MyInt{
  MyInt(int v):val(v){
    std::cout << "  Hello: " << val << std::endl;
  }
  ~MyInt(){
    std::cout << "  Good Bye: " << val << std::endl;
  }
  int val;
};

int main(){

  std::cout << std::endl;

  shared_ptr<MyInt> sharPtr(new MyInt(1998));
  std::cout << "    My value: " << sharPtr->val << std::endl;
  std::cout << "sharedPtr.use_count(): " << sharPtr.use_count() << std::endl;


  {
    shared_ptr<MyInt> locSharPtr(sharPtr);
    std::cout << "locSharPtr.use_count(): " << locSharPtr.use_count() << std::endl;
  }
  std::cout << "sharPtr.use_count(): "<<  sharPtr.use_count() << std::endl;

  shared_ptr<MyInt> globSharPtr= sharPtr;
  std::cout << "sharPtr.use_count(): "<<  sharPtr.use_count() << std::endl;
  globSharPtr.reset();
  std::cout << "sharPtr.use_count(): "<<  sharPtr.use_count() << std::endl;

  sharPtr= shared_ptr<MyInt>(new MyInt(2011));

  std::cout << std::endl;
  
}

 

Here is the screenshot of the program.

sharedPtr 

I create in line 22 MyInt(1998). This is the resource the smart pointer should take care of. By using sharPtr->val I have directly access to the resource (line 23). The output of the program shows the numbers of the reference counter. It starts in line 24 with one, becomes by the local copy shartPtr in line 28 two and goes after the block (line 27 - 40) back to one. The copy assignment in line 33 as a reset call modifies the reference counter. The expression sharPtr= shared_ptr<MyInt>(new MyInt(2011)) in line 38 is more interesting. Firstly, the resource MyInt(2011) is created and assigned to sharPtr. Consequently, the destructor of sharPtr is invoked. sharedPtr was the exclusive owner of the resource new MyInt(1998) (line 22). The last resource new MyInt(2011) will be destroyed at the end of main.

The program should not be too challenging. Now we can dig deeper.

The control block

The std::shared_ptr's share more than a resource and a reference counter. They share a resource and a control block. The control block has two counters and eventually more data. Two counters? The control block has a counter for the std::shared_ptr and the std::weak_ptr referencing the std::shared_ptr. That's the first time I'm speaking about the std::weak_ptr. Their job is it to break cyclic references. I will write a separate post about cyclic reference. Once more the overview.

The control block has

  • a counter for the std::shared_ptr.
  • a counter for the std::weak_ptr.
  • eventually further data like a special deleter or an allocator.

If you create std::shared_ptr together with its resource, two allocations are necessary. One for the resource and one for the control block. std::make_shared makes one allocation out of the two and is therefore faster (see: Memory and performance overhead of smart pointers) and safe. You have not this safety guarantee for std::shared_ptr<int>(new int(2011)). If you create a smart pointer with std::shared_ptr<int>(new int(2011)) one of the allocations may fail and you have a memory leak.

The std::shared_ptr can be parametrized by a special deleter. Exactly that happens in the next section of this post.

The deleter

The deleter of the std::shared_ptr is in opposite to the deleter of a std::unique_ptr not component of the type. Therefore, you can quite easily push std::shared_ptr with different deleters onto a std::vector<std::shared_ptr<int>>. The special deleter will be stored in the control block.

I create in the next example a special std::shared_ptr that logs how much memory has already been released.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// sharedPtrDeleter.cpp

#include <iostream>
#include <memory>
#include <random>
#include <typeinfo>

template <typename T>
class Deleter{
public:
  void operator()(T *ptr){
    ++Deleter::count;
    delete ptr;
  }
  void getInfo(){
    std::string typeId{typeid(T).name()};
    size_t sz= Deleter::count * sizeof(T);
    std::cout << "Deleted " << Deleter::count << " objects of type: " << typeId << std::endl;
    std::cout <<"Freed size in bytes: "  << sz << "." <<  std::endl;
    std::cout << std::endl;
  }
private:
  static int count;
};

template <typename T>
int Deleter<T>::count=0;

typedef Deleter<int> IntDeleter;
typedef Deleter<double> DoubleDeleter;

void createRandomNumbers(){

  std::random_device seed;

  std::mt19937 engine(seed());

  std::uniform_int_distribution<int> thousand(1,1000);
  int ranNumber= thousand(engine);
  for ( int i=0 ; i <= ranNumber; ++i) std::shared_ptr<int>(new int(i),IntDeleter());

}

int main(){

  std::cout << std::endl;

  {
    std::shared_ptr<int> sharedPtr1( new int,IntDeleter() );
    std::shared_ptr<int> sharedPtr2( new int,IntDeleter() );
    auto intDeleter= std::get_deleter<IntDeleter>(sharedPtr1);
    intDeleter->getInfo();
    sharedPtr2.reset();
    intDeleter->getInfo();

  }
  createRandomNumbers();
  IntDeleter().getInfo();

  {
    std::unique_ptr<double,DoubleDeleter > uniquePtr( new double, DoubleDeleter() );
    std::unique_ptr<double,DoubleDeleter > uniquePtr1( new double, DoubleDeleter() );
    std::shared_ptr<double> sharedPtr( new double, DoubleDeleter() );

    std::shared_ptr<double> sharedPtr4(std::move(uniquePtr));
    std::shared_ptr<double> sharedPtr5= std::move(uniquePtr1);
    DoubleDeleter().getInfo();
  }

  DoubleDeleter().getInfo();

}

 

Deleter in line 8 - 27 is the special deleter. The deleter is parametrized by the type T. It counts with the static variable count (line 23), how often the call operator (line 11 - 14) was used. Deleter returns all the information with getInfo (line 15 - 21).  The function createRandomNumbers (line 32 - 42) creates between 1 to 1000 std::shared_ptr (line 40) parametrized by the special deleter intDeleter().

The first usage of intDeleter->getInfo() shows that no resource has been released. This changes with the call sharedPtr2.reset() in line 53. A int variable with 4 bytes has been released. The call createRandomNumbers() in line 57 creates 74 std::shared_ptr<int>. Of course, you can use the deleter for a std::unique_ptr (line 60 - 68). The memory for the double objects will be released after the end of the block in line 68.

sharedPtrDeleter

What's next?

std::shared_ptr has a lot more to offer. You can create a std:.shared_ptr to an already existing object. std::shared_ptr has minimum multithreading guarantees. But one question is still not answered. Should your function take a std::shared_ptr by value or by reference? Get the answers in the next post.

 

 

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 

 

 

Comments   

0 #41 Genie 2017-07-29 23:42
Thanks for the terrific manual
Quote
0 #42 Randal 2017-08-02 11:56
I like the article
Quote
0 #43 Jimmy 2017-08-02 12:23
Thanks for the great guide
Quote
0 #44 Gita 2017-08-02 14:24
It works very well for me
Quote
0 #45 Marcus 2017-08-02 15:55
It works really well for me
Quote
0 #46 Doreen 2017-08-02 22:44
Thanks, it's very informative
Quote
0 #47 William 2017-08-03 03:26
This is actually helpful, thanks.
Quote
0 #48 Ali 2017-08-03 12:56
Thank you for the terrific post
Quote

Add comment


My Newest E-Books

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 378

All 459291

Currently are 157 guests and no members online