17.9 Class unique_ptr and Dynamic Memory Allocation

A common programming practice is to allocate dynamic memory, assign the address of that memory to a pointer, use the pointer to manipulate the memory and deallocate the memory with delete (or delete[]) when the memory is no longer needed. If an exception occurs after successful memory allocation but before the delete statement executes, a memory leak could occur. C++11 provides class template unique_ptr in header <memory> to deal with this situation. A unique_ptr maintains a pointer to dynamically allocated memory. When a unique_ptr object goes out of scope, its destructor is called, which performs a delete (or delete[]) operation on the unique_ptr object’s pointer data member. Class template unique_ptr provides overloaded operators * and -> so that a unique_ptr object can be used just like a regular pointer variable.

Demonstrating unique_ptr

Figure 17.9 demonstrates a unique_ptr object that points to a dynamically allocated object of our custom class Integer (Figs. 17.717.8).

Fig. 17.7 Integer class definition.

Alternate View

 1   // Fig. 17.7: Integer.h
 2   // Integer class definition.
 3
 4   class Integer {
 5   public:
 6      Integer(int i = 0); // Integer default constructor
 7      ~Integer(); // Integer destructor
 8      void setInteger(int i); // set Integer value
 9      int getInteger() const; // return Integer value
10   private:
11      int value;
12   };

Fig. 17.8 Integer member function definitions.

Alternate View

 1   // Fig. 17.8: Integer.cpp
 2   // Integer member function definitions.
 3   #include <iostream>
 4   #include "Integer.h"
 5   using namespace std;
 6
 7   // Integer default constructor
 8   Integer::Integer(int i)
 9      : value{i} {
10      cout << "Constructor for Integer " << value << endl;
11   }
12
13   // Integer destructor
14   Integer::~Integer() {
15      cout << "Destructor for Integer " << value << endl;
16   }
17
18   // set Integer value
19   void Integer::setInteger(int i) {
20      value = i;
21   }
22
23   // return Integer value
24   int Integer::getInteger() const {
25       return value;
26   }

Line 14 of Fig. 17.9 creates unique_ptr object ptrToInteger and initializes it with a pointer to a dynamically allocated Integer object that contains the value 7. To initialize the unique_ptr, line 14 uses C++14’s make_uniquefunction template, which allocates dynamic memory with operator new, then returns a unique_ptr to that memory. Prior to C++14, you’d pass the result of a new expression directly to unique_ptr’s constructor.

Fig. 17.9 unique_ptr object manages dynamically allocated memory.

Alternate View

 1   // Fig. 17.9: fig17_09.cpp
 2   // Demonstrating unique_ptr.
 3   #include <iostream>
 4   #include <memory>
 5   using namespace std;
 6
 7   #include "Integer.h"
 8   
 9   // use unique_ptr to manipulate Integer object
10   int main() {
11      cout << "Creating a unique_ptr object that points to an Integer
";
12
13      // "aim" unique_ptr at Integer object
14      unique_ptr<Integer> ptrToInteger{make_unique<Integer>(7)};
15
16      cout << "
Using the unique_ptr to set the Integer
";
17      ptrToInteger->setInteger(99); // use unique_ptr to set Integer value
18
19      // use unique_ptr to get Integer value
20      cout << "Integer after setInteger: " << (*ptrToInteger).getInteger()
21          << "

Terminating program" <<  endl;
22   }

Creating a unique_ptr object that points to an Integer
Constructor for Integer 7

Using the unique_ptr to set the Integer
Integer after setInteger: 99

Terminating program
Destructor for Integer 99

Line 17 uses the unique_ptr overloaded -> operator to invoke function setInteger on the Integer object that ptrToInteger manages. Line 20 uses the unique_ptr overloaded * operator to dereference ptrToInteger, then uses the dot (.) operator to invoke function getInteger on the Integer object. Like a regular pointer, a unique_ptr’s -> and * overloaded operators can be used to access the object to which the unique_ptr points.

Because ptrToInteger is a non-static local variable in main, it’s destroyed when main terminates. The unique_ptr destructor deletes the dynamically allocated Integer object, which calls the Integer object’s destructor. The memory that Integer occupies is released, regardless of how control leaves the block (e.g., by a return statement or by an exception). Most importantly, using this technique can prevent memory leaks. For example, suppose a function returns a pointer aimed at some object. Unfortunately, the function caller that receives this pointer might not delete the object, thus resulting in a memory leak. However, if the function returns a unique_ptr to the object, the object will be deleted automatically when the unique_ptr object’s destructor gets called.

17.9.1 unique_ptr Ownership

The class is called unique_ptr because only one unique_ptr at a time can own a dynamically allocated object. When you assign one unique_ptr to another, the unique_ptr on the assignment’s right transfers ownership of the dynamic memory it manages to the unique_ptr on the assignment’s left. The same is true when one unique_ptr is passed as an argument to another unique_ptr’s constructor. (These operations use unique_ptr’s move assignment operator and move constructor—we discuss move semantics in Chapter 24.) The last unique_ptr object that maintains the pointer to the dynamic memory will delete the memory. This makes unique_ptr an ideal mechanism for returning dynamically allocated memory to client code. When the unique_ptr goes out of scope in the client code, the unique_ptr’s destructor deletes the dynamically allocated object—if the object has a destructor, it is called before the memory is returned to the system.

17.9.2 unique_ptr to a Built-In Array

You can also use a unique_ptr to manage a dynamically allocated built-in array. For example, consider the statement


unique_ptr<string[]> ptr{make_unique<string[]>(10)};

Because make_unique’s type is specified as string[], the function obtains a dynamically allocated built-in array of the number of elements specified by its argument (10). By default, the elements of arrays allocated with make_unique are initialized to 0 for fundamental types, to false for bools or via the default constructor for objects of a class—so in this case, the array would contain 10 string objects initialized with the empty string.

A unique_ptr that manages an array provides an overloaded [] operator for accessing the array’s elements. For example, the statement


ptr[2] = "hello";

assigns "hello" to the string at ptr[2] and the following statement displays that string


cout << ptr[2] << endl;
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset