That causes films[2] to no longer refer to the string. After an auto_ptr gives up ownership of an object, it no longer provides access to the object. When the program goes to print the string pointed to by films[2], it finds the null pointer, which apparently is an unpleasant surprise.
Suppose you go back to Listing 16.6 but use shared_ptr instead of auto_ptr. (You’ll need a compiler that supports the C++11 shared_ptr class.) Then the program runs fine and gives this output:
The nominees for best avian baseball film are
Fowl Balls
Duck Walks
Chicken Runs
Turkey Errors
Goose Eggs
The winner is Chicken Runs!
The difference occurs in this part of the program:
shared_ptr
pwin = films[2];
This time both pwin and films[2] point to the same object, and the reference count is upped from 1 to 2. At the end of the program, pwin, which was declared last, is the first object to have its destructor called. The destructor decreases the reference count to 1. Then the members of the array of shared_ptrs are freed. The destructor for films[2] decrements the count to 0 and frees the previously allocated space.
So with shared_ptr, Listing 16.6 runs fine. With auto_ptr it experienced a runtime crash. What happens if you use unique_ptr? Like auto-ptr, unique_ptr incorporates the ownership model. Yet instead of crashing, the unique_ptr version yields a compile-time error objecting to this line:
pwin = films[2];
Clearly, it is time to look further into the differences between these last two types.
Why unique_ptr Is Better than auto_ptr
Consider the following statements:
auto_ptr
auto_ptr
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
unique_ptr
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
{
unique_ptr
return temp;
}
And suppose we then have this code:
unique_ptr
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
pu3 = unique_ptr
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_ptrs are banned (by recommendation, not by the compiler) from being used in container objects, whereas unique_ptrs are allowed. If a container algorithm tries to do something along the lines of assignment #1 to the contents of a container of unique_ptrs, 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_ptrs, cases like assignment #1 could lead to undefined behavior and mysterious crashes.