uniquePtr

std::unique_ptr

According to the RAII idiom, a std::unique_ptr manages automatically and exclusively the lifetime of its resource. std::unique_ptr should be your first choice because it does its work without memory or performance overhead.

 

Before I show you the usage of std::unique_ptr, I will present its characteristic in a few bullet points.

std::unique_ptr

  • can be instantiated with and without resources.
  • manages the life cycle of a single object but an array of objects.
  • transparently offers the interface of the underlying resource.
  • can be parametrized with its deleter function.
  • can be moved (move semantic).
  • can be created with the helper function std::make_unique.

The usage

The critical question of the std::unique_ptr is when to delete the underlying resource. This happens precisely when the std::unique_ptr goes out of scope or gets a new resource. Here are the two use cases.

 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
// uniquePtr.cpp

#include <iostream>
#include <memory>
#include <utility>

struct MyInt{

  MyInt(int i):i_(i){}

  ~MyInt(){
    std::cout << "Good bye from " << i_ << std::endl;
  }

  int i_;

};


int main(){

  std::cout << std::endl;

  std::unique_ptr<MyInt> uniquePtr1{ new MyInt(1998) };

  std::cout << "uniquePtr1.get(): " << uniquePtr1.get() << std::endl;

  std::unique_ptr<MyInt> uniquePtr2;
  uniquePtr2= std::move(uniquePtr1);
  std::cout << "uniquePtr1.get(): " << uniquePtr1.get() << std::endl;
  std::cout << "uniquePtr2.get(): " << uniquePtr2.get() << std::endl;

  std::cout << std::endl;


  {
    std::unique_ptr<MyInt> localPtr{ new MyInt(2003) };
  }

  std::cout << std::endl;

  uniquePtr2.reset(new MyInt(2011));
  MyInt* myInt= uniquePtr2.release();
  delete myInt;

  std::cout << std::endl;

  std::unique_ptr<MyInt> uniquePtr3{ new MyInt(2017) };
  std::unique_ptr<MyInt> uniquePtr4{ new MyInt(2022) };

  std::cout << "uniquePtr3.get(): " << uniquePtr3.get() << std::endl;
  std::cout << "uniquePtr4.get(): " << uniquePtr4.get() << std::endl;

  std::swap(uniquePtr3, uniquePtr4);

  std::cout << "uniquePtr3.get(): " << uniquePtr3.get() << std::endl;
  std::cout << "uniquePtr4.get(): " << uniquePtr4.get() << std::endl;

  std::cout << std::endl;

}

 

The class MyInt (lines 7 -17) is a simple wrapper for a number. I have adjusted the destructor in lines 11 – 13 for observing the life cycle of MyInt.

I create in line 24 a std::unique_ptr and return in line 27 the address of its resource (new MyInt(1998)). Afterward, I move the uniquePtr1 to uniquePtr2 (line 29). Therefore, uniquePtr2 is the owner of the resource. That shows the output of the program in lines 30 and 31. The local std::unique_ptr in line 37 reaches the end of the scope of its valid range. Therefore, the destructor of the localPtr – that means the destructor of the resource (new MyInt(2003)) – will be executed. Here is the screenshot.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

Be part of my mentoring programs:

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (starts March 2024)
  • Do you want to stay informed: Subscribe.

     

    uniquePtr

    The most interesting lines are lines 42 to 44. At first, I assign the uniquePtr1 a new resource. Therefore, the destructor of MyInt(1998) will be executed. After releasing the resource in line 43, I can explicitly invoke the destructor.

    The rest of the program is relatively easy to get. I create in lines 48 – 58 two std::unique_ptr and swap their resources. std::swap uses under-the-hood move semantic because std::unique_ptr supports no copy semantic. With the end of the main function, uniquePtr3 and uniquePtr4 go out of scope, and their destructor will automatically be executed.

    That was the big picture. Let’s dig into a few details of std::unique_ptr.

    Dealing with the lifetime of objects and arrays

    std::unique_ptr has a specialization for arrays. The access is transparent. That means if the std::unique_ptr manages the lifetime of an object, the operators for the object access are overloaded (operator* and operator->); if std::unique_ptr manages the lifetime of an array, the index operator operator[] is overloaded. The invocations of the operators are, therefore, totally transparent and forwarded to the underlying resource.

     

     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
    // uniquePtrArray.cpp
    
    #include <iomanip>
    #include <iostream>
    #include <memory>
    
    class MyStruct{
    public:
      MyStruct(){
        std::cout << std::setw(15) << std::left << (void*) this << " Hello "  << std::endl;
      }
      ~MyStruct(){
        std::cout << std::setw(15) << std::left << (void*)this << " Good Bye " << std::endl;
      }
    };
    
    int main(){
        
      std::cout << std::endl;
        
      std::unique_ptr<int> uniqInt(new int(2011));
      std::cout << "*uniqInt: " << *uniqInt << std::endl;
    
      std::cout << std::endl;
    
      {
        std::unique_ptr<MyStruct[]> myUniqueArray{new MyStruct[5]};
      }
    
      std::cout << std::endl;
    
      {
        std::unique_ptr<MyStruct[]> myUniqueArray{new MyStruct[1]};
        MyStruct myStruct;
        myUniqueArray[0]=myStruct;
      }
    
      std::cout << std::endl;
    
      {
        std::unique_ptr<MyStruct[]> myUniqueArray{new MyStruct[1]};
        MyStruct myStruct;
        myStruct= myUniqueArray[0];
      }
    
      std::cout << std::endl;
    
    }
    

     

    I dereference in line 22 the std::unique_ptr and get the value of its resource.

    MyStruct in lines 7 – 15 is the base of an array of std::unique_ptr’s. If I instantiate a MyStruct object, I will get its address. The destructor gives the output. Now it’s quite easy to observe the life cycle of the objects.

     uniquePtrArray

    I create and destroy in lines 26 – 28 five instances of MyStruct. Lines 32 – 36 are more interesting. I create a MyStruct instance on the heap (line 33) and the stack (line 34). Therefore, both objects have addresses from different ranges. Afterward, I assign the local object to the std::unique_pr (line 35). Lines 40 – 54 follow a similar strategy. Now I assign the local object the first element of myUniqueArray. The index access to the std::unique_ptr in lines 35 and 43 feels like familiar index access to an array.

    User-supplied deleters

    std::unique_ptr can have a user-supplied deleter: std::unique_ptr<int,MyDeleter> uniqPtr(new int(2011), intDeleter). The deleter is part of the type. You can use callables like functions, function objects, or lambda functions. If the deleter has no state, it will not change the size of the std::unique_ptr. If the deleter is a function object with a state or a lambda function that captures its context by value, the no-overhead principle will not hold anymore. I will write about the deleter in my post about std::shared_ptr.

    Replacement for std::auto_ptr

    Classical C++ already has std::auto_ptr. Its job is similar to the job of std::unique_ptr. std::auto_ptr exclusively manages the lifetime of its underlying resource. But std::auto_ptr is very strange. If you copy a std::auto_ptr, its resource will be moved. That means an operation with copy semantic performs under the hood move semantic. That’s why std::auto_ptr is deprecated, and you should instead use std::unique_ptr. std::unique_ptr can only be moved but not copied. You have to invoke std::move on a std::unique_ptr explicitly.

    The graphic shows the difference between std::auto_ptr and std::unique_ptr.

    If I execute the following code snippet,

    std::auto_ptr<int> auto1(new int(5));
    std::auto_ptr<int> auto2(auto1); 

    autoPtrCopy

    the std::auto_ptr auto1 will lose its resource.

    std::unique_ptr can’t be copied. Therefore, you have to use move semantics.

    std::unique_ptr<int> uniqueo1(new int(5));
    std::unique_ptr<int> unique2(std::move(unique1));
    

     

     uniquePtrCopy

    std::unique_ptr can be moved into the containers of the STL and afterward be used in the algorithm of the STL if they do not use copy semantics internally.

    To be precise. The copying of a std::auto_ptr is undefined behavior. The moving of std::unqiue_ptr puts the source in a well-defined but not exactly specified state. But the depicted behavior is quite likely.

    The helper function std::make_unique

    In C++11, we have std::make_shared but not std::make_unique. This is fixed with C++14. Although Microsoft Visual Studio 2015 officially supports C++11, you can use std::make_unique. Thanks to std::make_unique, you do not have to touch new.

    std::unique_ptr<int> uniqPtr1= std::make_unique<int>(2011);
    auto uniqPtr2= std::make_unique<int>(2014);
    

     

    If you use std::make_unique in combination with automatic type deduction, your typing is reduced to its bare minimum. That proves std::unique_ptr uniqPtr2.

    Always use std::make_unique

    There is another subtle reason to use std::make_unique. std::make_unique is always correct.

    If you use

    func(std::make_unique<int>(2014), functionMayThrow());
    func(std::unique_ptr<int>(new int(2011)), functionMayThrow());
    

     

    and functionMayThrow throws, you have a memory leak with new int(2011) for this possible sequence of calls:

    new int(2011)
    functionMayThrow()
    std::unique_ptr<int>(...)
    

    What’s next?

    The next post is about std::shared_ptr. Therefore, this post was about exclusive ownership, and the next post will be about shared ownership.

     

     

     

     

     

     

     

     

    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, Kris Kafka, 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, moon, Philipp Lenk, Hobsbawm, and Charles-Jianye Chen.

    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

    Seminars

    I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

    Standard Seminars (English/German)

    Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

    • C++ – The Core Language
    • C++ – The Standard Library
    • C++ – Compact
    • C++11 and C++14
    • Concurrency with Modern C++
    • Design Pattern and Architectural Pattern with C++
    • Embedded Programming with Modern C++
    • Generic Programming (Templates) with C++
    • Clean Code with Modern C++
    • C++20

    Online Seminars (German)

    Contact Me

    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 *