More C++ Idioms/Concrete Data Type

Concrete Data Type

edit

Intent

edit

To control object's scope and lifetime by allowing or disallowing dynamic allocation using the free store (heap)

Also Known As

edit

Motivation

edit

C++ provides two ways of controlling lifetime of an object and binding it with a program level identifier (variable). First is a scoped variable and an object, which are destroyed immediately after the scope ends (e.g., function scope integers). Second is a scoped variable (often a pointer) and a dynamically allocated object in the free store. In this case, at the end of the scope of the variable, the variable ceases to exist but the object's lifetime continues (e.g., singletons, a window object). It is possible to force the choice of the lifetime of an object either first way or the second using the Concrete Data Type idiom.

Solution and Sample Code

edit

This idiom simply uses class level access modifiers (private, protected) to achieve the goal. The following code shows how a MouseEventHandler class forces dynamic allocation.

class EventHandler 
{
  public:
    virtual ~EventHandler () {}
};
class MouseEventHandler : public EventHandler // Note inheritance
{
  protected:
    ~MouseEventHandler () {} // A protected virtual destructor.
  public:
    MouseEventHandler () {} // Public Constructor.
};
int main (void)
{
  MouseEventHandler m; // A scoped variable is not allowed as destructor is protected.
  EventHandler *e = new MouseEventHandler (); // Dynamic allocation is allowed
  delete e;  // Polymorphic delete. Does not leak memory.
}

Another way to force dynamic allocation is to prevent direct access to the constructor and instead provide a static function instance() to return a dynamically allocated object. It is in many ways similar to the Singleton design pattern. Moreover, it is not strictly necessary to use polymorphic delete to reclaim memory. A member function destroy() can serve the purpose saving the space required for a v-table pointer.

class MouseEventHandler // Note no inheritance
{
  protected:
    MouseEventHandler () {} // Protected Constructor.
    ~MouseEventHandler () {} // A protected, non-virtual destructor.
  public:
    static MouseEventHandler * instance () { return new MouseEventHandler(); }
    void destroy () { delete this; }  // Reclaim memory.
};

An opposite extreme of this idiom is to force scoped variable (a.k.a. automatic variable) only. It can be achieved using a private new operator.

class ScopedLock
{
  private:
    static void * operator new (size_t size); // Disallow dynamic allocation
    static void * operator new (size_t, void * mem);  // Disallow placement new as well.
};
int main (void)
{
   ScopedLock s; // Allowed
   ScopedLock * sl = new ScopedLock (); // Standard new and nothrow new are not allowed.
   void * buf = ::operator new (sizeof (ScopedLock));
   ScopedLock * s2 = new(buf) ScopedLock;  // Placement new is also not allowed
}

ScopedLock object can't be allocated dynamically with standard uses of new operator, nothrow new, and the placement new.

Known Uses

edit
edit

References

edit