The Mystery of the Missing Pointer

What was auto_ptr?

The class auto_ptr added smart pointer functionality to C++. It implemented a simple form of RAII. It did this by wrapping a normal (raw) pointer, and calling delete on the pointer in it’s destructor. So, the typical programmer (whether stubborn or not) could use it to manage a raw pointer, and be sure that delete would be called on the raw pointer when the auto_ptr went out of scope.

The implementation looked a little something like this:

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

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

    auto_ptr<T>& operator=(auto_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;
    }

    ~auto_ptr()
    {
        delete internal_pointer;
    }
}

The overloads for the operators operator* and operator-> allow us to treat an auto_ptr as if it were a raw pointer of type T. We can dereference straight to the members of the object of type T that the internal pointer points to. How this actually works is slightly subtle, but the important point is that it works!

This seems like a pretty useful class. So it might surprise you to learn that auto_ptr was deprecated in C++11 and then removed completely in C++17. Usually, there needs to be a pretty good reason to remove a feature like this. So why was it done?

Well, it turns out, that auto_ptr had a pretty huge flaw. Also a separate new feature was added to C++11 that meant a different, better implementation for this kind of smart pointer could be added.

What Was Wrong with auto_ptr?

An important point to understand is that we can only ever have one auto_ptr for a given raw pointer. Suppose we had two such auto_ptrs, when one of them went out of scope, it would delete the underlying memory, but the other auto_ptr would still have a raw pointer to that memory. Which could cause a use after delete or double delete. Which are both undefined behaviour in C++.

So we can’t really copy a auto_ptr. The copy constructor and assignment for auto_ptr could have been removed. This was done in C++ version before 11 by declaring the methods as private, and not giving an implementation. However, we want to be able to pass the auto_ptr into functions and to return it, which uses copying. So, we do a have a copy constructor and copy assignment. Let’s take a look at them:

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

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

These methods copy the internal pointer of rhs, and then set the value of the internal pointer in rhs to nullptr. In the copy assignment we also have to delete the internal pointer, as the object it points to is going out of scope. Neither of these are a copy in any meaningful sense of the world. When copying something, we would not expect the object we are copying to be modified. We would expect to end up with two identical copies of the original object. What this actually does is transfer ownership of the underlying pointer.

This was a problem. We’ve claimed to implement copy, but have done something entirely different. Imagine a photocopier, that produced a perfect copy of whatever document you fed it, but then shredded the original! This caused a particular problem with the standard containers like vector and standard algorithms like sort. The behaviour of these containers and algorithms relied on a normal non-destructive implementation of copy. It was possible to compile code that used auto_ptr with these containers and algorithms, but the resulting behaviour could be very bad!

What Replaced auto_ptr?

What we were trying to do in the copy constructor and copy assignment in auto_ptr was really a move, not a copy. With C++11 move semantics were built right into the language. This meant that a new type of smart pointer could be added, that managed a raw pointer and moved it rather than copying it. This new smart pointer was called unique_ptr. The standard containers and algorithms were also modified in C++11 to use move semantics, so it is possible to use them with unique_ptr.

Leave a Reply

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