sharedPtr

std::shared_ptr

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 explain the basics.

 

The Basics

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

sharedPtr

Thanks to shared1 shared2 are initialized. Ultimately, 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 (lines 8 – 16).

 

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

     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 direct 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 (lines 27 – 40) back to one. As a reset call, the copy assignment in line 33 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 the main.

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

    The control block

    The std::shared_ptr‘s share is 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 to break cyclic references. I will write a separate post about cyclic references. 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 do not have 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.

    A special deleter can parametrize the std::shared_ptr. Exactly that happens in the next section of this post.

    The deleter

    The deleter of the std::shared_ptr is opposite to that of a std::unique_ptr, not a component of the type. Therefore, you can 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 lines 8 – 27 is the special deleter. The type T. parametrizes the deleter It counts with the static variable count (line 23), how often the call operator (lines 11 – 14) was used. Deleter returns all the information with getInfo (lines 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. An 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 (lines 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.

     

     

     

     

     

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

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