unique_ptr
Is Better than auto_ptr
Consider the following statements:
auto_ptr<string> p1(new string("auto"); //#1
auto_ptr<string> p2; //#2
p2 = p1; //#3
When, in statement #3, p2
takes over ownership of the string
object, p1
is stripped of ownership. This, recall, is good because it prevents the destructors for both p1
and p2
from trying to delete the same object. But it also is bad if the program subsequently tries to use p1
because p1
no longer points to valid data.
Now consider the unique_ptr
equivalent:
unique_ptr<string> p3(new string("auto"); //#4
unique_ptr<string> p4; //#5
p4 = p3; //#6
In this case, the compiler does not allow statement #6, so we avoid the problem of p3
not pointing to valid data. Hence, unique_ptr
is safer (compile-time error versus potential program crash) than auto_ptr
.
But there are times when assigning one smart pointer to another doesn’t leave a dangerous orphan behind. Suppose we have this function definition:
unique_ptr<string> demo(const char * s)
{
unique_ptr<string> temp(new string(s));
return temp;
}
And suppose we then have this code:
unique_ptr<string> ps;
ps = demo("Uniquely special");
Here, demo()
returns a temporary unique_ptr
, and then ps
takes over ownership of the object originally owned by the returned unique_ptr
. Then the returned unique_ptr
is destroyed. That’s okay because ps
now has ownership of the string
object. But another good thing has happened. Because the temporary unique_ptr
returned by demo()
is soon destroyed, there’s no possibility of it being misused in an attempt to access invalid data. In other words, there is no reason to forbid assignment in this case. And, miraculously enough, the compiler does allow it!
In short, if a program attempts to assign one unique_ptr
to another, the compiler allows it if the source object is a temporary rvalue and disallows it if the source object has some duration:
using namespace std;
unique_ptr< string> pu1(new string "Hi ho!");
unique_ptr< string> pu2;
pu2 = pu1; //#1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string "Yo!"); //#2 allowed
Assignment #1 would leave a dangling unique_ptr
behind (that is, pu1
), a possible source of mischief. Assignment #2 leaves no unique_ptr
behind because it invokes the unique_ptr
constructor, which constructs a temporary object that is destroyed when ownership is transferred to pu3
. This selective behavior is one indication that unique_ptr
is superior to auto_ptr
, which would allow both forms of assignment. It’s also the reason that auto_ptr
s are banned (by recommendation, not by the compiler) from being used in container objects, whereas unique_ptr
s are allowed. If a container algorithm tries to do something along the lines of assignment #1 to the contents of a container of unique_ptr
s, you get a compile-time error. If the algorithm tries to do something like assignment #2, that’s okay, and the program proceeds. With auto_ptr
s, cases like assignment #1 could lead to undefined behavior and mysterious crashes.
Of course, it could be possible that you really want to do something like statement #1. The assignment is unsafe only if you use the abandoned smart pointer in an unsmart manner, such as dereferencing it. But you could safely reuse the pointer by assigning a new value to it. C++ has a standard library function called std::move()
that lets you assign one unique_ptr
to another. Here is an example using the previously defined demo()
function, which returns a unique_ptr<string>
object:
using namespace std;
unique_ptr<string> ps1, ps2;
ps1 = demo("Uniquely special");
ps2 = move(ps1); // enable assignment
ps1 = demo(" and more");
cout << *ps2 << *ps1 << endl;
You may be wondering how unique_ptr
, unlike auto_ptr
, is able to discriminate between safe and possibly unsafe uses. The answer is that it uses the C++11 additions of move constructors and rvalue references, as discussed in Chapter 18.
Also unique_ptr
has another advantage over auto_ptr
. It has a variant that can be used with arrays. Remember, you must pair delete
with new
and delete []
with new []
. The auto_ptr
template uses delete
, not delete []
, so it can only be used with new
, not with new []
. But unique_ptr
has a new[]
, delete[]
version:
std::unique_ptr< double[]>pda(new double(5)); // will use delete []
3.145.17.18