Generalization of auto_ptr<T> for working with Win32 API

STL has auto_ptr<Type> class that automatically deletes a dynamically allocated C++ object when control leaves a block. Personally, I believe that auto_ptr<Type> typically used in simple scenarios as a local variable or a class member but theoretically it is even possible to declare a vector of auto_ptr<Type> because auto_ptr<Type> stores an ownership indicator and its copy constructor transfers the ownership from the instance being copied, so vector::push_back(…) and vector::resize(…) functions works correctly.

About 10 years ago when I worked on some C++ projects I written a simple template class TOwner<A> that is a generalization of auto_ptr<Type>TOwner<A> owns an abstract resource described by an adapter of type A. It stores a handle of type A::Handle and releases the ownership of the resource designated by the handle by calling A::Destroy(Handle h) function. Approximately at the same time some other guy Cristian Vlasceanu did the same work, see his article Generalizing the Concepts Behind auto_ptr.

Below I provided the source code of TOwner<A> class and the source code of some adapters for resources of various types. They all are part of my AWL library and enclosed in awl namespace:

template <class A>
class TOwner
{
public:

  typedef A Adapter;
  typedef typename A::Handle Handle;

  static Handle Null() { return A::GetNull();}

  //The default constructor sets the stored handle to null and ownership indicator to true.
  explicit TOwner(const A & a = A()) : m_Adapter(a), m_bOwns(true),
    m_Handle(A::GetNull()) {}

  //An owner constructed with a handle to an object owns the object.
  TOwner(Handle h, const A & a = A()) : m_Adapter(a), m_bOwns(true),
    m_Handle(h) { }

  //Sets the stored handle to h and the ownership indicator to bOwns.
  TOwner(Handle h, bool bOwns, const A & a = A()) : m_Adapter(a),
    m_bOwns(bOwns), m_Handle(h) {}

  //The copy constructor sets the stored handle to the corresponding member of w and
  //transfers ownership from w.
  TOwner(const TOwner<A> & w) : m_Adapter(w.m_Adapter), m_bOwns(w.m_bOwns),
    m_Handle(w.Release()) {}

  TOwner<A> & operator = (Handle h)
  {
    if (m_Handle != h) {
      Destroy();    //delete the owned object
      m_Handle = h; //takes hold of the specified object
    }
    return *this;
  }

  TOwner<A> & operator = (const TOwner<A> & w)
  {
    if (this != &w) 
    {
      Assign(w.m_Handle, w.m_bOwns, w.m_Adapter);

      w.Release();
    }
    return *this;
  }

  void Assign(Handle h, bool bOwns, const A & a = A())
  {
    if (m_Handle != h)
    {
      //delete the owned object
      Destroy();

      m_Handle = h;

      //copy the adapter
      m_Adapter.operator = (a);
    }
    //transfer the ownership
    m_bOwns = bOwns;
  }

  ~TOwner() { Destroy();}

  //Indicates whether the stored handle is null.
  bool IsEmpty() const { return m_Handle == A::GetNull();}

  //Retrieves the stored handle value.
  Handle GetHandle() const { return m_Handle;}

  //Clears the ownership indicator returning the stored handle value.
  Handle Release() const
  {
    (const_cast<TOwner<A> *>(this))->m_bOwns = false;
    return m_Handle;
  }

  //Detaches the owned object without deleting them by setting the stored handle to null and
  //the ownership indicator to true. Returnes the handle to object to be detached.
  Handle Detach()
  {
    Handle h = m_Handle;
    CleanUp();
    return h;
  }

  //Deletes the object that it owns.
  void Destroy()
  {
    if (m_bOwns && !IsEmpty())
      AWL_VERIFY(m_Adapter.Destroy(m_Handle));
    CleanUp();
  }

  operator Handle () const { return m_Handle;}

  //The assert on operator& usually indicates a resource leak.
  //If this is really what is needed, however, call Detach() and try again.
  Handle * operator & ()
  {
    AWL_ASSERT(m_bOwns);
    AWL_ASSERT(IsEmpty());
    return &m_Handle;
  }


  Handle operator->() const
  {
    Handle h = GetHandle();
    AWL_ASSERT(h != Null());
    return h;
  }

private:

  //Declaration order of the data members is significant because Release() 
  //function used in initialization list produces a side effect.
  Adapter m_Adapter;  //adapter to operate on the owned object
  bool m_bOwns;       //ownership indicator
  Handle m_Handle;    //owned object handle

  //This member function is used instead of Detach() to avoid an operation 
  //with a dangling handle.
  void CleanUp()
  {
    m_bOwns = true;
    m_Handle = A::GetNull();
  }
};

Adapters and helper classes for working with GDI objects

template <class H>
class AGdiObject : public TAdapter< H >
{
public:

  typedef typename TAdapter<H>::Handle Handle;

  bool Destroy(Handle h) { return ::DeleteObject(h) != 0;}
};

template <class H>
class HGdiObject : public TOwner < AGdiObject < H > >
{
private:

  typedef TOwner < AGdiObject < H > > Base;

public:

  typedef typename Base::Adapter Adapter;
  typedef typename Base::Handle Handle;

  HGdiObject() {}

  HGdiObject(Handle h) : Base(h) {}

  HGdiObject(Handle h, bool bOwns) : Base(h, bOwns) {}

  HGdiObject(const HGdiObject & w) : Base(w) {}

  HGdiObject & operator=(Handle h)
  {
    Base::operator=(h);
    return *this;
  }

  HGdiObject & operator=(const HGdiObject & w)
  {
    Base::operator=(w);
    return *this;
  }
};

typedef HGdiObject<HRGN> HRegion;
typedef HGdiObject<HPEN> HPen;
typedef HGdiObject<HBRUSH> HBrush;
typedef HGdiObject<HBITMAP> HBitmap;
typedef HGdiObject<HFONT> HFont;

class TGdiSelector
{
protected:

  HDC m_hDC;

public:

  TGdiSelector(HDC hDC) : m_hDC(hDC)
  {
    AWL_ASSERT(hDC != NULL);
  }
};

template <class H>
class HGdiEntry : public TGdiSelector
{
protected:

  H m_hOldObj;

public:

  HGdiEntry(HDC hDC) : TGdiSelector(hDC)
  {
    m_hOldObj = static_cast<H>(::GetCurrentObject(hDC, HGdiObjectTraits<H>::GetObjectType()));
    AWL_ASSERT(m_hOldObj != NULL);
  }
  
  HGdiEntry(HDC hDC, H hObj) : TGdiSelector(hDC)
  {
    AWL_ASSERT(hObj != NULL);
    m_hOldObj = static_cast<H>(::SelectObject(m_hDC, hObj));
    AWL_ASSERT(m_hOldObj != NULL);
  }

  ~HGdiEntry() { AWL_VERIFY(::SelectObject(m_hDC, m_hOldObj) != NULL);}

  H operator = (H hObj)
  {
    AWL_ASSERT(hObj != NULL);
    H hOldObj = static_cast<H>(::SelectObject(m_hDC, hObj));
    AWL_ASSERT(hOldObj != NULL);
    return hOldObj;
  }
};

typedef HGdiEntry<HPEN> HPenEntry;
typedef HGdiEntry<HBRUSH> HBrushEntry;
typedef HGdiEntry<HBITMAP> HBitmapEntry;
typedef HGdiEntry<HFONT> HFontEntry;

template <class H>
class HGdiAttribute
{
private:

  typedef H Handle;

  HGdiObject<H> m_Owner;
  HGdiEntry<H> m_Selector;

public:

  HGdiAttribute(HDC hDC) : m_Selector(hDC)
  {
  }

  HGdiAttribute(HDC hDC, const HGdiObject<H> & w) : m_Owner(w), m_Selector(hDC, w) {}

  HGdiAttribute(HDC hDC, Handle h, BOOL bOwns) : m_Owner(h, bOwns), m_Selector(hDC, h) {}

  H operator = (H hObj) { m_Selector = hObj; m_Owner = hObj; return hObj;}

  const HGdiObject<H> & operator = (const HGdiObject<H> & w)
    { m_Selector = w; m_Owner = w; return m_Owner;}

  operator H () const { return m_Owner;}

  operator const HGdiObject<H> & () const { return m_Owner;}
};

typedef HGdiAttribute<HPEN> HSelPen;
typedef HGdiAttribute<HBRUSH> HSelBrush;
typedef HGdiAttribute<HBITMAP> HSelBitmap;
typedef HGdiAttribute<HFONT> HSelFont;

So, what for did I do that? The primary reason is to get rid of the annoying MFC classes such as CDC, CPen and CBrush that draw an ellipse with 7 lines of code. Furthermore, and this probably the worst, they are do not guarantee that the old GDI objects will be reselected if an exception is thrown:

// Let's assume that we have some CDC at this point
CDC *pDC = GetWindowDC();

// Create a solid gren brush.
CBrush brush(RGB(0, 255, 0));

// Create a solid red pen of width 2.
CPen pen(PS_SOLID, 2, RGB(255,0,0));

//Now I need select my objects to DC
CBrush* pOldBrush = pDC->SelectObject(&brush);
CPen* pOldPen = pDC->SelectObject( &pen );

pDC->Ellipse(CRect(100, 100, 200, 200));

// and then reselect stored objects.
pDC->SelectObject( pOldPen );
pDC->SelectObject(pOldBrush);

If I use HSelBrush and HSelPen I need only 3 lines:

// Let's assume that we have some HDC at this point
HDC hDC = ::GetWindowDC(hSomeWnd);

// Create a solid gren brush and select it to DC.
awl::HSelBrush hBrush(hDC, awl::CreateSolidBrush(RGB(0, 255, 0)));

// Create a solid gren brush and select it to DC
awl::HSelPen hPen(hDC, awl::CreatePen(PS_SOLID, 2, RGB(0, 255, 0)));

::Ellipse(hDC, CRect(100, 100, 200, 200));

//All work is done!

//I can even draw another ellipse with only 2 lines of code:

// Create and select another Pen, old pen will be deselected and deleted automatically
hPen = awl::CreatePen(PS_SOLID, 2, RGB(0, 0, 255));

::Ellipse(hDC, CRect(150, 150, 250, 250));

//GDI objects will be deselected and deleted automatically by destructors of hBrush and hPen

Adapters for Windows Kernel Objects

class AKernelObject : public TAdapter<HANDLE>
{
public:

  typedef TAdapter<HANDLE>::Handle Handle;

  bool Destroy(Handle h) { return ::CloseHandle(h) != 0;}
};

typedef TOwner<AKernelObject> HKernelObject;
typedef HKernelObject HFileMapping;
typedef HKernelObject HThread;

Leave a Reply

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