12.1.5. unique_ptr

Image

A unique_ptr “owns” the object to which it points. Unlike shared_ptr, only one unique_ptr at a time can point to a given object. The object to which a unique_ptr points is destroyed when the unique_ptr is destroyed. Table 12.4 lists the operations specific to unique_ptrs. The operations common to both were covered in Table 12.1 (p. 452).

Table 12.4. unique_ptr Operations (See Also Table 12.1 (p. 452))

Image

Unlike shared_ptr, there is no library function comparable to make_shared that returns a unique_ptr. Instead, when we define a unique_ptr, we bind it to a pointer returned by new. As with shared_ptrs, we must use the direct form of initialization:

unique_ptr<double> p1;  // unique_ptr that can point at a double
unique_ptr<int> p2(new int(42)); // p2 points to int with value 42

Because a unique_ptr owns the object to which it points, unique_ptr does not support ordinary copy or assignment:

unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1);  // error: no copy for unique_ptr
unique_ptr<string> p3;
p3 = p2;                    // error: no assign for unique_ptr

Although we can’t copy or assign a unique_ptr, we can transfer ownership from one (nonconst) unique_ptr to another by calling release or reset:

// transfers ownership from p1 (which points to the string Stegosaurus) to p2
unique_ptr<string> p2(p1.release()); // release makes p1 null
unique_ptr<string> p3(new string("Trex"));
// transfers ownership from p3 to p2
p2.reset(p3.release()); // reset deletes the memory to which p2 had pointed

The release member returns the pointer currently stored in the unique_ptr and makes that unique_ptr null. Thus, p2 is initialized from the pointer value that had been stored in p1 and p1 becomes null.

The reset member takes an optional pointer and repositions the unique_ptr to point to the given pointer. If the unique_ptr is not null, then the object to which the unique_ptr had pointed is deleted. The call to reset on p2, therefore, frees the memory used by the string initialized from "Stegosaurus", transfers p3’s pointer to p2, and makes p3 null.

Calling release breaks the connection between a unique_ptr and the object it had been managing. Often the pointer returned by release is used to initialize or assign another smart pointer. In that case, responsibility for managing the memory is simply transferred from one smart pointer to another. However, if we do not use another smart pointer to hold the pointer returned from release, our program takes over responsibility for freeing that resource:

p2.release(); // WRONG: p2 won't free the memory and we've lost the pointer
auto p = p2.release(); // ok, but we must remember to delete(p)

Passing and Returning unique_ptrs

There is one exception to the rule that we cannot copy a unique_ptr: We can copy or assign a unique_ptr that is about to be destroyed. The most common example is when we return a unique_ptr from a function:

unique_ptr<int> clone(int p) {
    // ok: explicitly create a unique_ptr<int> from int*
    return unique_ptr<int>(new int(p));
}

Alternatively, we can also return a copy of a local object:

unique_ptr<int> clone(int p) {
    unique_ptr<int> ret(new int (p));
    // . . .
    return ret;
}

In both cases, the compiler knows that the object being returned is about to be destroyed. In such cases, the compiler does a special kind of “copy” which we’ll discuss in § 13.6.2 (p. 534).

Passing a Deleter to unique_ptr

Like shared_ptr, by default, unique_ptr uses delete to free the object to which a unique_ptr points. As with shared_ptr, we can override the default deleter in a unique_ptr12.1.4, p. 468). However, for reasons we’ll describe in § 16.1.6 (p. 676), the way unique_ptr manages its deleter is differs from the way shared_ptr does.

Overridding the deleter in a unique_ptr affects the unique_ptr type as well as how we construct (or reset) objects of that type. Similar to overriding the comparison operation of an associative container (§ 11.2.2, p. 425), we must supply the deleter type inside the angle brackets along with the type to which the unique_ptr can point. We supply a callable object of the specified type when we create or reset an object of this type:

// p points to an object of type objT and uses an object of type delT to free that object
// it will call an object named fcn of type delT
unique_ptr<objT, delT> p (new objT, fcn);

As a somewhat more concrete example, we’ll rewrite our connection program to use a unique_ptr in place of a shared_ptr as follows:

void f(destination &d /* other needed parameters */)
{
    connection c = connect(&d);  // open the connection
    // when p is destroyed, the connection will be closed
    unique_ptr<connection, decltype(end_connection)*>
        p(&c, end_connection);
    // use the connection
    // when f exits, even if by an exception, the connection will be properly closed
}

Here we use decltype2.5.3, p. 70) to specify the function pointer type. Because decltype(end_connection) returns a function type, we must remember to add a * to indicate that we’re using a pointer to that type (§ 6.7, p. 250).


Exercises Section 12.1.5

Exercise 12.16: Compilers don’t always give easy-to-understand error messages if we attempt to copy or assign a unique_ptr. Write a program that contains these errors to see how your compiler diagnoses them.

Exercise 12.17: Which of the following unique_ptr declarations are illegal or likely to result in subsequent program error? Explain what the problem is with each one.

int ix = 1024, *pi = &ix, *pi2 = new int(2048);
typedef unique_ptr<int> IntP;

(a) IntP p0(ix);

(b) IntP p1(pi);

(c) IntP p2(pi2);

(d) IntP p3(&ix);

(e) IntP p4(new int(2048));

(f) IntP p5(p2.get());

Exercise 12.18: Why doesn’t shared_ptr have a release member?


..................Content has been hidden....................

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