Using mutable keyword to implement an owner-pointer class (C++03) For a couple of days, I have been trying to implement an own templated class called owner_ptr (for my little project) whose instances has ownership over a dynamically allocated object (or array). I want this template to support the deletion of dynamically allocated arrays too and also make it possible to pass ownership between owner_ptr objects. I've tried out many things and ideas (the last one was this) but each of them had some drawbacks.

The necessity of passing ownership between owner_ptr objects causes that you must modify the pointer of the passed owner_ptr object during copy-constructing or assignment (you have to set the pointer to NULL). Copy-constructor and assignment operator with const-reference is necessary to make this class work with std::vector.

template &lt;typename T&gt;
class owner_ptr
{
    /* Private members */

public:
    owner_ptr(const owner_ptr&lt;T&gt;&amp;);
    owner_ptr&amp; operator=(const owner_ptr&lt;T&gt;&amp;);

   /* Other public member functions */
}


But how can I modify the objects if I have const-reference to them? The problem can be solved if I store the encapsulated pointer as a mutable member variable.

Here is my implementation:

template &lt;typename T&gt;
class owner_ptr
{
    mutable T* p;

    T* get_ownership() const
    {
        T* tmp = p;
        p = NULL;
        return tmp;
    }

public:
    explicit owner_ptr(T* ptr = NULL) : p(ptr) {}
    owner_ptr(const owner_ptr&lt;T&gt;&amp; orig) : p(orig.get_ownership()) { }

    owner_ptr&amp; operator=(const owner_ptr&lt;T&gt;&amp; rhs)
    {
        if (this != &amp;rhs)
        {
            this-&gt;pass(rhs.get_ownership());
        }
        return *this;
    }

    void pass(T* ptr = NULL)
    {
        if (p != ptr)
        {
            delete p;
            p = ptr;
        }
    }

    T* get() const { return p; }
    T* operator-&gt;() const { return p; }
    T&amp; operator*() const { return *p; }

    ~owner_ptr()
    {
        this-&gt;pass();
    }
};


And a specialization for arrays (almost the same):

template &lt;typename T&gt;
class owner_ptr&lt;T[]&gt;
{
    mutable T* p;

    T* get_ownership() const
    {
        T* tmp = p;
        p = NULL;
        return tmp;
    }

public:
    explicit owner_ptr(T* ptr = NULL) : p(ptr) {}
    owner_ptr(const owner_ptr&lt;T[]&gt;&amp; orig) : p(orig.get_ownership()) { }

    owner_ptr&amp; operator=(const owner_ptr&lt;T[]&gt;&amp; rhs)
    {
        if (this != &amp;rhs)
        {
            this-&gt;pass(rhs.get_ownership());
        }
        return *this;
    }

    void pass(T* ptr = NULL)
    {
        if (p != ptr)
        {
            delete[] p;
            p = ptr;
        }
    }

    T* get() const { return p; }
    T* operator-&gt;() const { return p; }
    T&amp; operator*() const { return *p; }
    T&amp; operator[](int i) const { return p[i]; }

    ~owner_ptr()
    {
        this-&gt;pass();
    }
};


I tested the code and it seems to be working. std::vector also accepts this template, so I can use it for creating heterogeneous containers.

My questions:


Is it a bad idea to use mutable to solve the ownership-passing
problem? Are there any scenarios where it can cause undefined behaviour?
Is there anything that I implemented incorrectly in the template?
Any affect on performance?
Before C++11, why did we have only the silly std::auto_ptr which couldn't be used with std::vector?


NOTE: I'm doing this for learning purposes. This question is totally independent from the features and smart-pointers of C++11.
OK. Lets ignore that you are screwing around with mutable.

No Strong Exception Guarantee.

void pass(T* ptr = NULL)
{
    if (p != ptr)
    {
        delete[] p;   // if p throws then your object is left in
                      // an invalid state and you have leaked the
                      // pointer `ptr`
        p = ptr;
    }
}


You should do this in an exception safe way. Which requires the use of a temporary.

void pass(T* ptr = NULL)
{
    if (p != ptr)
    {
        T* tmp = p;
        p = ptr;
        delete[] tmp;   // now it is safe to delete.
                        // even if it throws this object is consistent
                        // and you have not leaked ptr.
    }
}


Double ownership check

Both this method and the previous check for self assignment. Because this function calls the above function you perform a self assignment check twice when you use the assignment operator.

owner_ptr&amp; operator=(const owner_ptr&lt;T&gt;&amp; rhs)
{
    if (this != &amp;rhs)  // Check here
    {
        this-&gt;pass(rhs.get_ownership());  // Check inside pass()
    }
    return *this;
}


I would change both of these methods to call a private method that does the check and work.

owner_ptr&amp; operator=(const owner_ptr&lt;T[]&gt;&amp; rhs)
{
    private_pass(rhs.get_ownership());
    return *this;
}

void pass(T* ptr = NULL)
{
    private_pass(ptr);
}
private:
void private_pass(T* ptr)
{
    if (p != ptr)
    {
        T* tmp = p;
        p = ptr;
        delete[] tmp;
    }
}


No Throwing methods

Since the get_ownership() method is guaranteed not to throw you should probably mark it as such.

T* get_ownership() throw();


Questions:


  Is it a bad idea to use mutable to solve the ownership-passing problem? 


Probably. Mutable is really for designating members that are not part of the objects actual state (ie temporary caches).


  Are there any scenarios where it can cause undefined behaviour?


Don't think so. You will just loose out on certain optimizations.


  Is there anything that I implemented incorrectly in the template?


Don't see anything.


  Any affect on performance?


Yes. As noted above the compiler can not apply certain optimizations when a member is marked mutable.


  Before C++11, why did we have only the silly std::auto_ptr which couldn't be used with std::vector?


Because the language did not have the features required to implement modern smart pointers. The auto_ptr was the first attempt at a smart pointer and it worked to a certain extent it just had limitations.

It took them 8 years to improve the language enough so that smart pointers could be implemented properly.