Code This

std::cout <<me.ramble() <<std::endl;

Automatic Deallocation With AutoPtr

with one comment

One of the major concepts in C++ that makes it so powerful, and therefore so difficult, is memory management.  Even experienced programmers sometimes struggle with allocating and deallocating memory correctly and effectively.  However, if done correctly (which is, of course, rather subjective), C++ will always be more efficient than any garbage collected language will ever be.

I recently ran into a memory management issue.  I was employing a partial caching strategy, so in certain scenarios I wanted to return a pointer to memory stored in cache, and in other scenarios I needed to allocated new memory to return because the data did not exist in cache.  This left me with two options: copy the data, or try to deal with deallocating the memory in the latter case.  I chose to deal with deallocating the memory.

Initially, I considered std::auto_ptr.  Why reinvent the wheel for no good reason?  However, std::auto_ptr did not provide facilities for specifying that the auto_ptr did not own the memory upon construction, which is something I needed to be able to specify.  For this reason, and simply for the learning opportunity, I wrote my own version of an auto_ptr class with the functionality I needed (I realize I could have simply inherited std::auto_ptr to provide this functionality, but how fun would that have been).  Here is the source code for this class:

template<typename _T>
class AutoPtr
{
   template<typename _T1>
   friend class AutoPtr;

public:
   //------------------------------------------------------------------------------------------------------
   // Function: Constructor
   //
   // Parameters:
   //   ptr - the allocated pointer managed by this class
   //   owned - whether the memory should be deleted upon destruction
   //------------------------------------------------------------------------------------------------------
   explicit AutoPtr(_T* ptr = 0, bool owned = true)
      : m_owned(owned),
        m_ptr(ptr) { }
   //------------------------------------------------------------------------------------------------------
   // Function: Copy constructor
   //
   // Parameters:
   //   other - object to copy (will be disaccosiated from the managed pointer)
   //------------------------------------------------------------------------------------------------------
   AutoPtr(AutoPtr<_T>& other)
      : m_owned(false),
        m_ptr(0) {
      // Can't use the initializer list here because some compilers initialize from the bottom
      // up, which incorrectly sets m_owned to false.
      m_owned = other.m_owned;
      m_ptr = other.Detach();
   }
   //------------------------------------------------------------------------------------------------------
   // Function: Copy constructor
   //   Similar to above copy constructor, but works for types that are convertible to _T.
   //
   // Parameters:
   //   other - object to copy (will be dissociated from the managed pointer)
   //------------------------------------------------------------------------------------------------------
   template<typename _T1>
   AutoPtr(AutoPt<_T1>& other)
      : m_owned(false),
        m_ptr(0) {
      // Can't use the initializer list here because some compilers initialize from the bottom
      // up, which incorrectly sets m_owned to false.
      m_owned = other.m_owned;
      m_ptr = other.Detach();
   }
   //------------------------------------------------------------------------------------------------------
   // Function: Destructor
   //------------------------------------------------------------------------------------------------------
   ~AutoPtr() {
      if (m_owned) delete m_ptr;
   }

   _T* operator->() { return m_ptr; }
   _T& operator*() { return *m_ptr; }
   operator _T*() { return m_ptr; }
   const _T* operator->() const { return m_ptr; }
   const _T& operator*() const { return *m_ptr; }
   operator const _T*() const { return m_ptr; }

   //------------------------------------------------------------------------------------------------------
   // Function: operator=
   //
   // Parameters:
   //   lhs - object to copy (will be disaccosiated from the managed pointer)
   //------------------------------------------------------------------------------------------------------
   AutoPt& operator=(AutoPtr& lhs) {
      bool owned = lhs.m_owned;
      Reset(lhs.Detach(), owned);
      return *this;
   }
   //------------------------------------------------------------------------------------------------------
   // Function: operator=
   //   Similar to above operator=, but works for types that are convertable to _T.
   //
   // Parameters:
   //   other - object to copy (will be disaccosiated from the managed pointer)
   //------------------------------------------------------------------------------------------------------
   template<typename _T1>
   AutoPtr& operator=(AutoPtr<_T1>& lhs) {
      bool owned = lhs.m_owned;
      Reset(lhs.Detach(), owned);
      return *this;
   }

   //------------------------------------------------------------------------------------------------------
   // Function: Detach
   //   Disassociates the managed pointer from this instance.
   //
   // Parameters:
   //   None
   //
   // Returns:
   //   _T*
   //------------------------------------------------------------------------------------------------------
   _T* Detach() {
      _T* t = m_ptr;
      m_ptr = 0;
      m_owned = false;
     return t;
   }
   //------------------------------------------------------------------------------------------------------
   // Function: Reset
   //   Updates this instance to manage a new allocated pointer. Will free any previously owned pointer.
   //
   // Parameters:
   //   ptr - the allocated pointer managed by this class
   //   owned - whether the memory should be deleted upon destruction
   //
   // Returns:
   //   None
   //------------------------------------------------------------------------------------------------------
   void Reset(_T* ptr = 0, bool owned = true) {
      if (m_owned) delete m_ptr;
      m_owned = owned;
      m_ptr = ptr;
   }

protected:
   // variable: m_owned (protected)
   //   Whether or not we own the memory (and should therefore deallocate it).
   bool m_owned;

   // variable: m_ptr (protected)
   //   The pointer.
   _T* m_ptr;

protected:
   // The following is a helper class and supporting methods that allow AutoPtr to
   // support reference semantics. They are not intended to be consumed publically.
   struct PtrWrapper
   {
      bool m_owned;
      _T* m_ptr;

      explicit PtrWrapper(bool owned, _T* ptr)
         : m_owned(owned),
           m_ptr(ptr) { }
   };

public:
   AutoPtr(PtrWrapper wrapper)
      : m_owned(wrapper.m_owned),
        m_ptr(wrapper.m_ptr) { }
   AutoPt& operator=(PtrWrapper lhs) {
      Reset(lhs.m_ptr, lhs.m_owned);
      return *this;
   }

   operator PtrWrapper() {
      bool owned = m_owned;
      return PtrWrapper(owned, Detach());
   }
};

Now, this type of thing has been done a thousand times in the past, but that’s OK. I’ll take the opportunity to walk through the code anyway. I’ll assume you have at least some basic template knowledge.

We start with a basic constructor and copy constructor, easy enough.  Next is something slightly more interesting:

template<typename _T1>
AutoPtr(AutoPtr<_T1>& other);

This is a copy constructor that allows us to copy from an AutoPtr of a convertible type.  i.e., An AutoPtr of a child class type being passed to the constructor of an AutoPtr of it’s parent class type.  We then have some simple operators that give the class pointer semantics.  These operators are what allow us to treat the AutoPtr class just like a real pointer.  We then have 2 operator=’s, which are exactly like the 2 copy constructors.

Next we have:

_T* detach();

This method explicitly takes ownership of the managed memory from the AutoPtr instance. It is, of course, used in the copy constructors and operator=’s.  And:

void reset(_T* ptr = 0, bool owned = true);

Which instructs the AutoPtr instance to manage new memory, first destroying any previously managed memory.

The ReferenceHelper type is also interesting.  This simple struct gives AutoPtr reference semantics:

AutoPtr<MyClass> getMyClass()
{
   return AutoPtr<MyClass>(new MyClass);
}

int main()
{
   AutoPtr ptr = getMyClass();
}

Without this struct, we would not be able to properly manage the new’d instance of MyClass when returning from getMyClass.  What actually happens here is:

AutoPtr is implicitly converted to ReferenceHelper

ReferenceHelper is implicitly converted to AutoPtr.

This allows us to correctly remember whether or not we own the allocated memory, while not destructing it when returning from getMyClass.

This class is especially useful in complex methods that “save” data, where errors can essentially happen at any point.  If memory is allocated and managed with AutoPtr, we do not have to worry about cleaning up the allocated memory on various different code branches.

Advertisements

Written by Kris Wong

October 6, 2008 at 9:25 am

One Response

Subscribe to comments with RSS.

  1. Great info! As usual, I am honored that you are able to articulate and “speak” about this wonderful art that has transformed our world during the “Information and Innovation Age”! It’s a great talent, appreciate you sharing/this post!

    Brian Siegel

    October 6, 2008 at 2:38 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: