unique_ptr
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_ptr
s. The operations common to both were covered in Table 12.1 (p. 452).
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_ptr
s, 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)
unique_ptr
sThere 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).
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_ptr
(§ 12.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 decltype
(§ 2.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).
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?