The basic thing to remember with auto_ptr is that it is supposed to improve the safety of handling dynamically allocated objects within block structured code. What does that mean? Let's look at an example:
file_spy * smiley = new file_spy(); ... smiley->espionage(); ... delete smiley;The problem with this code is that espionage() may throw an exception. If it does then the flow of control will never reach the delete expression and we will have a memory leak. One answer to this problem is to create a class whose destructor deletes the allocated object [1]. For example:
// owned_ptr.hpp namespace accu { template<typename type> class owned_ptr { public: explicit owned_ptr(type * acquire); ... ~owned_ptr(); public: // query type * operator->() const; type & operator*() const; type * get() const; ... private: // state type * resource; }; } #include "owned_ptr-template.hpp"
// owned_ptr-template.hpp namespace accu { template<typename type> owned_ptr<type>::owned_ptr(type * acquire) : resource(acquire) { // all done } template<typename type> owned_ptr<type>::~owned_ptr() { delete resource; } ... template<typename type> type * owned_ptr<type>::operator->() const { return get(); } template<typename type> type & owned_ptr<type>::operator*() const { return *get(); } ... }Now the example can be re-written as:
owned_ptr<file_spy> smiley(new file_spy()); ... smiley->espionage();and if espionage() throws an exception, the owned_ptr destructor will delete the dynamic file_spy, thus plugging the memory leak. The interesting part is when you try to flesh out owned_ptr to make it "minimal but complete". The most obvious missing parts are the copy constructor and the assignment operator. The usual signatures for these are:
namespace accu
{
template<typename type>
class owned_ptr
{
...
owned_ptr(const owned_ptr & other);
owned_ptr & operator=(const owned_ptr & rhs);
...
};
}
The problem, in both of these signtures, is the presence of the const.
Suppose you write the copy constructor like this:
namespace accu
{
template<typename type>
owned_ptr<type>::owned_ptr(const owned_ptr & other)
: resource(other.resource)
{
// all done
}
...
}
This is a sure fire recipe for disaster. You'll end up
with multiple owned_ptr objects pointing to the same dynamically
created object. Every owned_ptr object will attempt to delete the
same object in its destructor.
For example:
void dangle(owned_ptr<snafu> fubar)
{
...
}
owned_ptr<snafu> oops(new snafu(42));
dangle(oops);
Here dangle will copy construct the fubar parameter from
oops and when dangle returns it will call the destructor
for the copy constructed fubar parameter, thus deleting the
snafu pointed to by oops. When oops goes out
of scope it too will have its destructor called and it too will try to
delete the object already deleted by the dangle parameter. How
can you solve this problem? One way is to keep a count of how many "owners"
are pointing to the resource. This is reference counting. However, owned_ptr
takes a different approach. Instead owned_ptr ensures that a dynamically
allocated object is only ever pointed to by one owner. In other words, if
you write this:
snafu * p = new snafu(42); owned_ptr<snafu> alpha(p); owned_ptr<snafu> beta(alpha);Then owned_ptr has to ensure that either alpha or beta ends up owning p, but not both. Given this it might seem logical to implement the copy constructor like this:
namespace accu
{
template<typename type>
owned_ptr<type>::owned_ptr(const owned_ptr & other)
: resource(other.resource)
{
other.resource = 0;
}
}
The idea here is that other passes the resource on to the
newly constructed object. This won't work. The problem is that
other is declared const — so the compiler won't
allow the line:
...
other.resource = 0;
...
You might try and fix this by making the copy constructor and copy
assignment operator take a non-const reference (exactly as
auto_ptr does):
namespace accu
{
template<typename type>
owned_ptr<type>::owned_ptr(owned_ptr & other)
: resource(other.resource)
{
other.resource = 0;
}
template<typename type>
owned_ptr<type>
owned_ptr<type>::operator=(owned_ptr & rhs)
{
delete resource;
resource = rhs.resource;
rhs.resource = 0;
return *this;
}
}
This solves the problem, but up pops another (an ominous sign).
The apparently legal code:
owned_ptr<snafu> f(); // function declaration ... owned_ptr<snafu> delta(f()); // copy constructionnow fails to compile. It fails because the copy constructor argument is an unnamed temporary object returned by f() and the C++ standard states that temporary objects cannot be bound to non-const reference parameters [2]. Okay, I hear you say, that's easy to work around, make the parameter a named non-temporary object. Like this:
owned_ptr<snafu> temp; temp = f(); owned_ptr<snafu> delta(temp);This won't work either. The reason is that the assignment is really just syntactic sugar for this:
temp.operator=(f());and, once again, the parameter is an unnamed temporary. So, giving a non-const reference to the copy constructor and copy assignment operator of owned_ptr has had a surprising result: it has made it impossible to return an owned_ptr object from a function by copy. At this point I hope you feel owned_ptr is losing its way. It seems to be lurching from one problem to another, and the hack-work-arounds are simply making matters worse. Time to take a step back and start thinking again. Remember that the original motivation was to improve the exception safety of dynamically objects in block structured code. In this light, returning an owned_ptr from a function by copy can be seen as an attempt to misuse owned_ptr. A reasonable approach is therefore to revoke the copy constructor and the copy assignment operator completely. One way to do this is to inherit the inability to copy [3]:
namespace accu
{
class non_copyable
{
protected:
non_copyable() {}
private:
non_copyable(const non_copyable &);
non_copyable & operator=(const non_copyable &);
};
}
...
namespace accu
{
template<typename type>
class owned_ptr : public non_copyable
{
...
};
}
But this is perhaps too cute. I prefer this:
namespace accu
{
template<typename type>
class owned_ptr
{
...
private: // inappropriate
owned_ptr(const owned_ptr &);
owned_ptr & operator=(const owned_ptr &);
...
};
}
What else needs doing to owned_ptr? Well, it's reasonable
to provide a default constructor (to mirror a raw pointer).
It also seems reasonable to provide methods to explicitly control ownership.
// owned_ptr.hpp namespace accu { template<typename type> class owned_ptr { public: // birth/death explicit owned_ptr(type * acquire = 0); ~owned_ptr(); public: // query type * operator->() const; type & operator*() const; type * get() const; ... public: // ownership void transfer(owned_ptr & acquire_from); void swap(owned_ptr & swap_with); void reset(type * new_resource = 0); void release(); private: // inappropriate owned_ptr(const owned_ptr &); owned_ptr & operator=(const owned_ptr &); private: // state type * resource; }; } #include "owned_ptr-template.hpp"
// owned_ptr-template.hpp ... namespace accu // owned_ptr - birth/death { template<typename type> owned_ptr<type>::owned_ptr(type * acquire) : resource(acquire) { // all done } template<typename type> owned_ptr<type>::~owned_ptr() { delete resource; } } namespace accu // owned_ptr - query { template<typename type> type * owned_ptr<type>::operator->() const { return get(); } template<typename type> type & owned_ptr<type>::operator*() const { return *get(); } template<typename type> type * owned_ptr<type>::get() const { if (resource == 0) { throw ... } return resource; } } namespace accu // owned_ptr - ownership { template<typename type> void owned_ptr<type>::transfer(owned_ptr & acquire_from) { reset(acquire_from.release()); } template<typename type> void owned_ptr<type>::swap(owned_ptr & rhs) { std::swap(resource, rhs.resource); } template<typename type> void owned_ptr<type>::reset(type * new_resource) { type * old_resource = resource; resource = new_resource; delete old_resource; } template<typename type> type * owned_ptr<type>::release() { type * released = resource; resource = 0; return released; } }Finally, lets see a small example of owned_ptr is action. Suppose we have a design where a publisher object is responsible for notifying all registered subscriber objects when a resource changes [4]. Further, suppose that the subscriber objects need only retain a copy of the most up to date resource:
class resource;
class subscriber
{
public:
virtual void update(const resource & updated) = 0;
};
class example : public subscriber
{
public:
...
private:
virtual void update(const resource & updated);
owned_ptr<resource> latest;
...
};
void example::update(const resource & updated)
{
latest.reset(new resource(updated));
}
That's all for now.
Cheers
Jon Jagger
jon@jaggersoft.com