std::unique_ptr

Contents[Show]

A std::unique_ptr manages automatically and exclusively the lifetime of its resource according to the RAII idiom. 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 you in a few bullet points its characteristic.

std::unique_ptr

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

The usage

The key question of the std::unique_ptr is it when to delete the underlying resource. This happen exactly 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 (line 7 -17) is a simple wrapper for a number. I have adjusted the destructor in line 11 - 13 for observing the life cycle of MyInt.

I create in line 24 a std::unique_ptr and return in the line 27 the address of its resource (new MyInt(1998)). Afterwards, I move the uniquePtr1 to uniquePtr2 (line 29). Therefore, uniquePtr2 is the owner of the resource. That shows the output of program in line 30 and 31. The local std::unique_ptr in line 37 reaches with the end of the scope 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.

uniquePtr

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

The rest of the program is quite easy to get. I create in the 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 goes out of scope and their destructor will automatically be executed.

That was the big picture. Let 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 totally 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 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 line 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 output Is given by the destructor. Now it's quite easy to observe the life cycle of the objects.

 uniquePtrArray

I create and destroy in the lines 26 - 28 five instances of MyStruct. The lines 32 - 36 are more interesting. I create a MyStruct instance on the heap (line 33) and on the stack (line 34). Therefore, both objects have addresses from different ranges. Afterwards, I assign the local object to the std::unique_pr (line 35). The lines 40 - 54 follows a similar stratey. Now I assign the local object the first element of myUniqueArray. The index access to the std::unique_ptr in the lines 35 and 43 feels like a 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 any more. 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 the reason 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 explicitly invoke std::move on a std::unique_ptr.

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 semantic.

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 afterwards be used in the algorithm of the STL if they do not use copy semantic internally.

To be precise. The copying of a std::auto_ptr is undefined behaviour. The moving of std::unqiue_ptr puts the source in a well defined but not exactly specified state. But the depictured behaviour 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 have not 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 proofs std::unique_ptr uniqPtr2.

Always use std::make_unique

There is another but 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.

 

 

 

 

 

 

 

 

 

 

 

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.

 

Add comment


My Newest E-Book

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 725

All 332849

Currently are 142 guests and no members online