auto_ptr is dead, long live unique_ptr!

We talked a bit about auto_ptr in a previous post. In particular we talked about the problem with copying auto_ptr. The auto_ptr class had a copy constructor and copy assignment. However as it was supposed to represent a single unique pointer, these were not actually copies at all. This would cause issues with algorithms that relied on the ability to make normal copies.

So, when C+11 was introduced, a new feature premiered, move semantics. We’ve talked a lot about move semantics already. A move is like a copy, but, you transfer ownership of a resource rather than copying it. That means that the source of the move, is not necessarily left in it’s original state.

Move semantics are just what was needed to fix auto_ptr, and that is how unique_ptr was born. The code looks something like this:

template<typename T>
class unique_ptr
{
private:
    T* internal_pointer;
public:
    unique_ptr(T* internal_pointer) : internal_pointer(internal_pointer)
    {
    }

    unique_ptr(unique_ptr& rhs) = delete;

    unique_ptr<T>& operator=(unique_ptr& rhs) = delete;

    unique_ptr(unique_ptr&& rhs) : internal_pointer(rhs.internal_pointer)
    {
        rhs.internal_pointer = nullptr;
    }

   unique_ptr<T>& operator=(unique_ptr&& rhs)
   {
      delete internal_pointer;
      internal_pointer = rhs.internal_pointer;
      rhs.internal_pointer = nullptr;
      return *this;
   }

   T& operator*()
   {
       return *internal_pointer;
   }

   T* operator->()
   {
       return internal_pointer;
   }

   ~unique_ptr()
   {
       delete internal_pointer;
    }
};

As you can see, this is very similar to our implementation of auto_ptr. There is one big difference. Instead of fake copies, we are using bona fide moves. Indeed, we don’t want to allow copies at all, so we delete the copy constructor and copy assignment explicitly:

   unique_ptr(unique_ptr& rhs) = delete;

   unique_ptr<T>& operator=(unique_ptr& rhs) = delete;

Now, if someone tries to copy a unique_ptr object, their code will not compile. To allow a unique_ptr object to be passed around, we have implemented a move constructor and move assignment:

   unique_ptr(unique_ptr&& rhs) : internal_pointer(rhs.internal_pointer)
   {
       rhs.internal_pointer = nullptr;
   }

   unique_ptr<T>& operator=(unique_ptr&& rhs)
   {
        delete internal_pointer;
        internal_pointer = rhs.internal_pointer;
        rhs.internal_pointer = nullptr;
        return *this;
   }

These have the same implementations as our copy constructor and copy assignment in auto_ptr. However, the && in the signature of these functions means they will only ever be called for a move, not a copy.

The move constructor and assignment will be used when we are constructor or assigning from an object that the compiler can see is just a temporary object. We can also use std::move to tell the compiler explicitly to move our unique pointer. For example, when we passed an auto_ptr to a function, it would have been copied, but with unique_ptr we can move it, like this:

class SomeClass
{
    // pretend there is some interesting code here
}

template<typename T>
void SomeFunction(unique_ptr<T> ptr)
{
    // imagine we do something with ptr here
}

int main()
{
    unique_ptr<SomeClass> ptr(new SomeClass());
    SomeFunction(std::move(ptr));
}

So we now have a safe way to manage a raw pointer. By using unique_ptr we can be sure that delete will be called for our raw pointer when the scope is unwound, we have a guarantee that the unique_ptr will not be copied, and we can move it when we need to.

4 Replies to “auto_ptr is dead, long live unique_ptr!”

  1. I think it’s a common practice that if a smart pointer is being passed by value, it denotes a modification of ownership. In your example, I would expect SomeFunction() to either delete, or assign ownership in some way.

    Relevant Guideline from Herb Sutter’s GOTW*: Don’t pass a smart pointer as a function parameter unless you want to use or manipulate the smart pointer itself, such as to share or transfer ownership.

    *https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/

    1. Hey, “DankYooper”. I don’t think I totally understand. Since this is a unique pointer, moving it into SomeFunction, is a transfer of ownership of the underlying pointer from the calling code to SomeFunction. Does this follow the guideline?

      How do you mean that SomeFunction would delete or assign ownership?

  2. Yes! I believe this has the correct calling semantics, passing a unique_ptr by value requires using std::move() to help show the original unique_ptr will be moved from. I don’t remember exactly what I was thinking since it was a while back (am I supposed to get emailed when a comment responds?), but since SomeFunction is now the owner of the unique_ptr, if it doesn’t do anything with it, it will be deleted when it goes out of scope. That might be what you intended, but for this example it doesn’t gain much, since it would be cleaned up at the end of main() anyway.

Leave a Reply

Your email address will not be published. Required fields are marked *