I l@ve RuBoard Previous Section Next Section

Solution

graphics/bulb_icon.gif

Most people have heard of the standard auto_ptr smart pointer facility, but not everyone uses it daily. That's a shame, because it turns out that auto_ptr neatly solves common C++ coding problems, and using it well can lead to more-robust code. This article shows how to use auto_ptr correctly to make your code safer梐nd how to avoid the dangerous but common abuses of auto_ptr that create intermittent and hard-to-diagnose bugs.

Why "Auto" Pointer?

auto_ptr is just one of a wide array of possible smart pointers. Many commercial libraries provide more-sophisticated kinds of smart pointers that can do wild and wonderful things, from managing reference counts to providing advanced proxy services. Think of the standard C++ auto_ptr as the Ford Escort of smart pointers梐 simple, general-purpose smart pointer that doesn't have all the gizmos and luxuries of special-purpose or high-performance smart pointers, but does many common things well and is perfectly suitable for regular use.

What auto_ptr does is own a dynamically allocated object and perform automatic cleanup when the object is no longer needed. Here's a simple example of code that's unsafe without auto_ptr.



// Example 1(a): Original code 


//


void f()


{


  T* pt( new T );


  /*...more code...*/


  delete pt;


}


Most of us write code like this every day. If f() is a three-line function that doesn't do anything exceptional, this may be fine. But if f() never executes the delete statement, either because of an early return or because of an exception thrown during execution of the function body, then the allocated object is not deleted and we have a classic memory leak.

A simple way to make Example 1(a) safe is to wrap the pointer in a "smarter," pointer-like object that owns the pointer and that, when destroyed, deletes the pointed-at object automatically. Because this smart pointer is simply used as an automatic object (that is, one that's destroyed automatically when it goes out of scope), it's reasonably called an "auto" pointer:



// Example 1(b): Safe code, with auto_ptr 


//


void f()


{


  auto_ptr<T> pt( new T );


  /*...more code...*/


} // cool: pt's destructor is called as it goes out


  // of scope, and the object is deleted automatically


Now the code will not leak the T object, no matter whether the function exits normally or by means of an exception, because pt's destructor will always be called during stack unwinding. The cleanup happens automatically.

Finally, using an auto_ptr is just about as easy as using a builtin pointer, and to "take back" the resource and assume manual ownership again, we just call release().



// Example 2: Using an auto_ptr 


//


void g()


{


  T* pt1 = new T;


  // right now, we own the allocated object


  // pass ownership to an auto_ptr


  auto_ptr<T> pt2( pt1 );


  // use the auto_ptr the same way


  // we'd use a simple pointer


  *pt2 = 12;       // same as "*pt1 = 12;"


  pt2->SomeFunc(); // same as "pt1->SomeFunc();"


  // use get() to see the pointer value


  assert( pt1 == pt2.get() );


  // use release() to take back ownership


  T* pt3 = pt2.release();


  // delete the object ourselves, since now


  // no auto_ptr owns it any more


  delete pt3;


} // pt2 doesn't own any pointer, and so won't


  // try to delete it... OK, no double delete


Finally, we can use auto_ptr's reset() function to reset the auto_ptr to own a different object. If the auto_ptr already owns an object, though, it first deletes the already-owned object, so calling reset() is much the same as destroying the auto_ptr and creating a new one that owns the new object.



// Example 3: Using reset() 


//


void h()


{


  auto_ptr<T> pt( new T(1) );


  pt.reset( new T(2) );


    // deletes the first T that was


    // allocated with "new T(1)"


} // finally, pt goes out of scope and


  // the second T is also deleted


Wrapping Pointer Data Members

Similarly, auto_ptr can be used to safely wrap pointer data members. Consider the following common example that uses the Pimpl (or compiler-firewall) Idiom.[2]

[2] The Pimpl Idiom is useful for reducing project build times because it prevents wide-ranging recompilations of client code whenever the private portions of C change. For more about the Pimpl Idiom and how best to deploy compiler firewalls, see Items 26 through 30.



// Example 4(a): A typical Pimpl 


//





// file c.h


//


class C


{


public:


  C();


  ~C();


  /*...*/


private:


  struct CImpl; // forward declaration


  CImpl* pimpl_;


};





// file c.cpp


//


struct C::CImpl { /*...*/ };


C::C() : pimpl_( new CImpl ) { }


C::~C() { delete pimpl_; }


In brief, C's private details are split off into a separate implementation object that's hidden behind an opaque pointer. The idea is that C's constructor is responsible for allocating the private helper Pimpl object that contains the class's hidden internals, and C's destructor is responsible for deallocating it. Using auto_ptr, however, we find an easier way:



// Example 4(b): A safer Pimpl, using auto_ptr 


//


// file c.h


//


class C


{


public:


  C();


  ~C();


  /*...*/


private:


  struct CImpl; // forward declaration


  auto_ptr<CImpl> pimpl_;


  C& operator = ( const C& );


  C( const C& );


};


// file c.cpp


//


struct C::CImpl { /*...*/ };


C::C() : pimpl_( new CImpl ) { }


C::~C() {}


Now the destructor doesn't need to worry about deleting the pimpl_ pointer, because the auto_ptr will handle it automatically. Better still, it means C::C() has to do less work to detect and recover from constructor failures because pimpl_ is always automatically cleaned up. This can be easier than managing the pointer manually, and it follows the good practice of wrapping resource ownership in objects梐 job that auto_ptr is well suited to do. We'll revisit this example again at the end.

Ownership, Sources, and Sinks

This is nifty stuff all by itself, but it gets better. It's also very useful to pass auto_ptrs to and from functions, as function parameters and return values.

To see why, first consider what happens when you copy an auto_ptr: An auto_ptr owns the object that it holds a pointer to, and only one auto_ptr may own an object at a time. When you copy an auto_ptr, you automatically transfer ownership from the source auto_ptr to the target auto_ptr; if the target auto_ptr already owns an object, that object is first freed. After the copy, only the target auto_ptr owns the pointer and will delete it in due time, while the source is set back to a null state and can no longer be used to refer to the owned object.

For example:



// Example 5: Transferring ownership from 


//            one auto_ptr to another


//


void f()


{


  auto_ptr<T> pt1( new T );


  auto_ptr<T> pt2;


  pt1->DoSomething(); // OK


  pt2 = pt1;  // now pt2 owns the pointer,


              // and pt1 does not


  pt2->DoSomething(); // OK


} // as we go out of scope, pt2's destructor


  // deletes the pointer, but pt1's does nothing


But be careful to avoid the pitfall of trying to use a nonowning auto_ptr:



// Example 6: Never try to do work through 


//            a non-owning auto_ptr


//


void f()


{


  auto_ptr<T> pt1( new T );


  auto_ptr<T> pt2;


  pt2 = pt1;  // now pt2 owns the pointer, and


              // pt1 does not


  pt1->DoSomething();


              // error: following a null pointer


}


With that in mind, we start to see how well auto_ptr works with sources and sinks. A "source" is a function or other operation that creates a new resource, and then it typically hands off and relinquishes ownership of the resource. A "sink" is a function that does the reverse梟amely, that takes ownership of an existing object (and typically disposes of it). Instead of just having sources and sinks return and take bald pointers, though, it's usually better to return or take a smart pointer that owns the resource.

This gets us to the first part of the Item's code:



auto_ptr<T> source() 


{


  return auto_ptr<T>( new T(1) );


}


void sink( auto_ptr<T> pt ) { }


This is both legal and safe. Note the elegance of what's going on here:

  1. source() allocates a new object and returns it to the caller in a completely safe way, by letting the caller assume ownership of the pointer. Even if the caller ignores the return value (of course, you would never write code that ignores return values, right?), the allocated object will always be safely deleted.

    See also Item 19, which demonstrates why this is an important idiom, since returning a result by wrapping it in something like an auto_ptr is sometimes the only way to make a function strongly exception-safe.

  2. sink() takes an auto_ptr by value and therefore assumes ownership of it. When sink() is done, the deletion is performed as the local auto_ptr object goes out of scope (as long as sink() itself hasn't handed off ownership to someone else). Because the sink() function as written above doesn't actually do anything with the parameter, calling "sink( pt );" is a fancy way of writing "pt.reset(0);".

    The next piece of code shows source() and sink() in action.

    
    
    void f() 
    
    
    {
    
    
      auto_ptr<T> a( source() );
    
    
    

This is again both legal and safe. Here f() takes ownership of the pointer received from source(), and (ignoring some problems later in f()) it will delete it automatically when the automatic variable goes out of scope. This is fine, and it's exactly how passing back an auto_ptr by value is meant to work.



sink( source() ); 


Once again, this is both legal and safe. Given the trivial (that is, empty) definitions of source() and sink() here, this is just a fancy way of writing "delete new T(1);". So is it really useful? Well, if you imagine source() as a nontrivial factory function and sink() as a nontrivial consumer, then yes, it makes a lot of sense and crops up regularly in real-world programming.



sink( auto_ptr<T>( new T(1) ) ); 


Still legal and safe. This is another fancy way of writing "delete new T(1);", and it's a useful idiom when sink() is a nontrivial consumer function that takes ownership of the pointed-to object.

But beware: Never use auto_ptrs except in one of the ways I've just described. I have seen many programmers try to use auto_ptrs in other ways, just as they would use any other object. The problem with this is that auto_ptrs are most assuredly not like any other object. Here's the fundamental issue, and I'll highlight it to make sure it stands out.

For auto_ptr, copies are NOT equivalent.

It turns out that this has important effects when you try to use auto_ptrs with generic code that does make copies and isn't necessarily aware that copies aren't equivalent (after all, copies usually are). Consider the following code that I regularly see posted on the C++ newsgroups:



vector< auto_ptr<T> > v; 


This is not legal, and it sure isn't safe! Beware梩his road is paved with good intentions.

It is never safe to put auto_ptrs into standard containers. Some people will tell you that their compiler and library compiles this fine, and others will tell you that they've seen exactly this example recommended in the documentation of a certain popular compiler. Don't listen to them.

The problem is that auto_ptr does not quite meet the requirements of a type you can put into containers, because copies of auto_ptrs are not equivalent. For one thing, there's nothing that says a vector can't just decide to up and make an "extra" internal copy of some object it contains. But hold on, because it's about to get worse.



v.push_back( auto_ptr<T>( new T(3) ) ); 


v.push_back( auto_ptr<T>( new T(4) ) );


v.push_back( auto_ptr<T>( new T(1) ) );


v.push_back( a );


(Note that copying a into v means that the a object no longer owns the pointer it's carrying. More on that in a moment.)



v.push_back( auto_ptr<T>( new T(2) ) ); 


sort( v.begin(), v.end() );


Illegal. Unsafe. When you call generic functions that will copy elements, like sort() does, the functions have to be able to assume that copies are going to be equivalent. At least one popular sort internally takes a copy of a "pivot" element, and if you try to make it work on auto_ptrs, it will merrily take a copy of the pivot auto_ptr object (thereby taking ownership and putting it in a temporary auto_ptr on the side), do the rest of its work on the sequence (including taking further copies of the now-nonowning auto_ptr that was picked as a pivot value), and when the sort is over, the pivot is destroyed and you have a problem. At least one auto_ptr in the sequence no longer owns the pointer it once held, and in fact the pointer it held has already been deleted.

So the standards committee bent over backward to do everything it could to help you out. The standard auto_ptr was deliberately and specifically designed to break if you try to use it with the standard containers (or, at least, to break with most natural implementations of the standard library). To do this, the committee used a trick: auto_ptr's copy constructor and copy assignment operator take references to non-const to the right-hand-side object. Most implementations of the standard containers' single-element insert() functions take a reference to const, and hence won't work with auto_ptrs.

The Scoop on Nonowning auto_ptrs



    // (after having copied a to another auto_ptr) 


  cout << a->Value();


}


(We'll assume that a was copied, but that its pointer wasn't deleted by the vector or the sort.) Copying an auto_ptr not only transfers ownership, but resets the source auto_ptr to null. This is done specifically to avoid letting anyone do anything through a nonowning auto_ptr. Using a nonowning auto_ptr like this is not legal, and trying to dereference a null pointer like this will result in undefined behavior (typically, a core dump or memory access exception on most systems).

This brings us to the last common usage of auto_ptr.

Wrapping Pointer Members



class C 


{


public:    /*...*/


protected: /*...*/


private:   /*...*/


  auto_ptr<CImpl> pimpl_;


};


Possible issue: One of the /*...*/ areas (whether public, protected, or private) had better include at least declarations for copy construction and copy assignment.

auto_ptrs are useful for encapsulating pointing member variables. This works very much like our motivating example at the beginning of this Item, except that instead of saving us the trouble of doing cleanup at the end of a function, it now saves us the trouble of doing cleanup in C's destructor.

There is still a caveat, of course: Just as if you were using a bald pointer data member instead of an auto_ptr member, you will have to supply your own destructor and copy constructor and copy assignment operator for the class (even if you disable them by making them private and undefined), because the default ones will do the wrong thing.

auto_ptr and Exception Safety

Finally, auto_ptr is sometimes essential to writing exception-safe code. Consider the following function:



// Exception-safe? 


//


String f()


{


    String result;


    result = "some value";


    cout << "some output";


    return result;


}


This function has two visible side effects: It emits some output, and it returns a String. A detailed examination of exception safety is beyond the scope of this Item, but the goal we want to achieve is the strong exception-safety guarantee, which boils down to ensuring that the function acts atomically梕ven if there are exceptions, either all side effects will happen or none of them will.

Although the above code comes pretty close to achieving the strong exception-safety guarantee, there's still one minor quibble, as illustrated by the following calling code:



String theName; 


theName = f();


The String copy constructor is invoked because the result is returned by value, and the copy assignment operator is invoked to copy the result into theName. If either copy fails, then f() has completed all of its work and all of its side effects (good), but the result has been irretrievably lost (oops).

Can we do better, and perhaps avoid the problem by avoiding the copy? For example, we could let the function take a non-const String reference parameter and place the return value in that:



// Better? 


//


void f( String& result )


{


  cout << "some output";


  result = "some value";


}


This may look better, but it isn't, because the assignment to result might still fail which leaves us with one side effect complete and the other incomplete. Bottom line, this attempt doesn't really buy us much.

One way to solve the problem is to return a pointer to a dynamically allocated String, but the best solution is to go a step farther and return the pointer in an auto_ptr:



// Correct (finally!) 


//


auto_ptr<String> f()


{


  auto_ptr<String> result = new String;


  •result = "some value";


  cout << "some output";


  return result;


      // rely on transfer of


      // ownership; this can't throw


}


This does the trick, since we have effectively hidden all of the work to construct the second side effect (the return value) while ensuring that it can be safely returned to the caller using only nonthrowing operations after the first side effect has completed (the printing of the message). We know that, once the cout is complete, the returned value will make it successfully into the hands of the caller, and be correctly cleaned up in all cases: If the caller accepts the returned value, the act of accepting a copy of the auto_ptr causes the caller to take ownership; and if the caller does not accept the returned value, say by ignoring the return value, the allocated String will be automatically cleaned up as the temporary auto_ptr holding it is destroyed. The price for this extra safety? As often happens when implementing strong exception safety, the strong safety comes at the (usually minor) cost of some efficiency梙ere, the extra dynamic memory allocation. But, when it comes to trading off efficiency for correctness, we usually ought to prefer the latter!

Make a habit of using smart pointers like auto_ptr in your daily work. auto_ptr neatly solves common problems and will make your code safer and more robust, especially when it comes to preventing resource leaks and ensuring strong exception safety. Because it's standard, it's portable across libraries and platforms, and so it will be right there with you wherever you take your code.

The const auto_ptr Idiom

Now that we've waded through the deeper stuff, here's a technique you'll find interesting. Among its other benefits, the refinement to auto_ptr also means that const auto_ptrs never lose ownership. Copying a const auto_ptr is illegal, and in fact the only things you can do with a const auto_ptr are dereference it with operator*() or operator->() or call get() to inquire about the value of the contained pointer. This means that we have a clear and concise idiom to express that an auto_ptr can never lose ownership.



const auto_ptr<T> pt1( new T ); 


    // making pt1 const guarantees that pt1 can


    // never be copied to another auto_ptr, and


    // so is guaranteed to never lose ownership


auto_ptr<T> pt2( pt1 ); // illegal


auto_ptr<T> pt3;


pt3 = pt1;              // illegal


pt1.release();          // illegal


pt1.reset( new T );     // illegal


Now that's what I call const. So if you want to declare to the world that an auto_ptr can never be changed and will always delete what it owns, this is the way to do it. The const auto_ptr idiom is a useful and common technique, and one you should keep in mind. The originally posted solution to this Guru of the Week issue concluded with the following words: "This const auto_ptr idiom is one of those things that's likely to become a commonly used technique, and now you can say that you knew about it since the beginning."

    I l@ve RuBoard Previous Section Next Section