Click here to Skip to main content
15,887,283 members
Articles / Programming Languages / C++

Constructional Patterns Template Class Library

Rate me:
Please Sign up or sign in to vote.
3.45/5 (6 votes)
18 Jul 2007CPOL8 min read 26.6K   327   25   2
Template classes to implement constructional patterns.

Contents

Introduction

This article builds on the ideas of the famous Patterns book: "Design Patterns, Elements of Reusable Object-Oriented Software" [^] by the so called Gang of Four. That's this GOF[^], not this one[^] . I don't want to post text from, or a plug for, the book here, so while this article should be useful even if you haven't read the book, it will probably make a lot more sense if you have.

Having read this extremely influential book a few years ago, I wondered whether a number of the creational patterns, in particular, could be abstracted in such a way that most of the work would be taken out of using them for future classes.

Some may feel that what I have done is over complex in introducing class templates, or not in the spirit of the book, because my classes do not map one to one to those represented in the book's examples. I believe that these patterns are just that, the shapes of generic solutions, and the exact mapping of patterns into classes is not a point of principle. Also, I find it easier to make use of a working piece of generic code than an indirectly reusable specific example of a good idea.

What I really wanted to start with was to be able to make my new class a singleton simply by adding a DECLARE_SINGLETON macro to the header and an IMPLEMENT_SINGLETON macro to the .cpp file, in much the same style as MFC or ATL. I present here a way to do precisely that and a little more. The infrastructure required to make this possible also provides an opportunity to extend the catalogue of creational patterns with a new pattern I call a Flyer.

Reference will be made to my previous article[^] which introduced the static Templated Object Block (sTOB), not perhaps a pattern in itself, but useful in the same area of 'class instancing' as the creational patterns. I use sTOB(s) in this code and elsewhere to prevent static initialisation order problems occurring when the code is reused for purposes I hadn't thought of when I wrote it.

The Patterns

The templates and classes presented are intentionally simplified. A more complete implementation would probably use STL data structures and allocators, much more synchronisation security, and error checking.

Singleton

Lots of Singleton implementations [^] have been published before, to the point where I hesitate to even include the code here. I do so only to show how the macros work which allow the features of a Singleton to be attached to a new class.

C++
//TSingleton.h
//Template for singleton classes
template< class T >
class CTSingleton
{
public:
    //Get a pointer to the one instance
    static inline T* Instance();    
    //Release a pointer to the one instance
    static inline void Release();

protected:
    //Only callable by subclasses not otherwise used!
    CTSingleton(){}            //Default construction
    virtual ~CTSingleton(){}    //Default destruction

private:
    //pseudo static pointer to the one instance
    Declare_pseudo_static( T* ) _ppInstance;
    //Reference count on client pointers to the one instance    
    Declare_pseudo_static( long ) plInstanceCount;    
    //A Critical Section to synchronise MT usage
    Declare_pseudo_static( CCriticalSection ) pCS;    
};

template< class T > inline T* CTSingleton< T >::Instance()
{
    pCS->Lock();

    //If we don't have one then
    if( ((T*)_ppInstance) == 0 )
    {
        //Create the one instance
        _ppInstance = new T();
    }

    //increment the reference count
    (*plInstanceCount)++;

    pCS->Unlock();
    return ((T*)_ppInstance);
}

template< class T > inline void CTSingleton< T >::Release()
{
    pCS->Lock();
    
    //decrement the reference count
    (*plInstanceCount)--;                        

    //If no more references are held and 
    //then Release is called again
    if( (*plInstanceCount) < 0 )
    {
        //The one instance is deleted
        delete ((T*)_ppInstance);                
        //reset pointer so re-creation can occur
        ((T*)_ppInstance) = 0;                    

        //Reference count is reset to 0        
        (*plInstanceCount) = 0;        
    }
    pCS->Unlock();
}

The only thing to note here beyond the use of sTOBs and the simple thread synchronisation is that these singletons are designed to be freed on the first unbalanced call to Release; in other words, when the reference count goes to -1 rather than when it goes to 0. This prevents heap thrashing in single-threaded one-use-at-a-time scenarios, and allows the developer precise control over the point of destruction.

Now to the MFC style macros:

C++
#define DECLARE_SINGLETON( _Class )\
\
friend CTSingleton< _Class >;\
protected:\
    _Class();\
    virtual ~##_Class##()

All this really does is prevent _Class from being created except by derived classes or CTSingleton< _Class >.

C++
#define IMPLEMENT_SINGLETON( _Class  )\
\
Implement_pseudo_static(_Class##*)\
    CTSingleton< _Class >::_ppInstance;\
Implement_pseudo_static(long)\
    CTSingleton< _Class >::plInstanceCount;\
Implement_pseudo_static(CCriticalSection)\
    CTSingleton< _Class >::pCS

This goes in the .cpp implementation file for the class being made a singleton, and initialises the static data.

When you come to use a Singleton, you need an easy way to get a pointer to it. To that end and to help make reference counting easy, I wrote CTSingletonPtr. It's a simple smart pointer class that wraps getting and releasing a CTSingleton.

C++
template<class T >
class CTSingletonPtr  
{
private:

    //NEVER CALLED private overrides to prevent heap 
    //allocation of instances of this class
    void* operator new(unsigned int){}
    void operator delete(void*){}

protected:

    T* m_pT;            //pointer to singleton instance

public:

    CTSingletonPtr()
    {
        m_pT = CTSingleton<T>::Instance();
        //get the instance pointer on creation
    }

    virtual ~CTSingletonPtr()
    {
        m_pT = 0;
        CTSingleton<T>::Release();
        //release it on destruction                
    }

    inline T* operator ->()
    {
        return m_pT;
        //provide member pointer access to the singleton
    }

    inline T& operator()()
    {
        return *m_pT;
        //provide functor style access to the singleton
    }
};

CTSingletonPtr<TSing> pSing; gets you a pointer that can be used just like any other.

Abstract Factory and Factory Method

I implement these two very similar concepts with two types of classes. A ClassInstanceFactory which, at its simplest, fulfills the role of a Factory Method, and rtabsfactory which, at its simplest, fulfills the role of the Abstract Factory.

The class instance factory concept is so useful and flexible that I use it to implement most of the other patterns.

Class Instance Factory

This comes in two parts. The first is a hierarchy of instance factory classes, starting with an abstract base. The second is a class registry which associates unique class identifiers with the factory which creates that class. In order to make the class registration system work, a way of attaching unique identifiers to classes is needed. I use a technique called 'poor man's RTTI' for this.

First, a GUID structure just like COM:

C++
typedef struct __mxGUID
{
    unsigned long int Data1;
    unsigned short int Data2;
    unsigned short int Data3;
    unsigned char Data4[8];
}const mxGUID, *mxPGUID;

Then, a macro to add a GUID to a class.

C++
//Declare a class unique identifier
#define DECLARE_OCLASS_ID(myClass)\
\
    static const mxGUID* myClass::ClassID(void);\
    static const char* myClass::TypeName(void)

Then, a macro to provide a default implementation of the GUID for a class:

C++
#define IMPLEMENT_OCLASS_LUID(myClass)\
\
const mxGUID* myClass::ClassID()\
{\
        static const mxGUID classID = \
    {0x0000, 0x00, 0x00,\
    {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}} ;\
        return &classID;\
}\
\
const char* myClass::TypeName()\
{\
    return #myClass ;\
}

This default implementation leaves the GUID value at 0, but that doesn't matter as we'd only need a real value if this class was somehow accessible externally to the process. Within the process, the address of this structure is sufficiently unique to behave as a Locally Unique Identifier (LUID), which is what we need.

Here is the abstract base class for instance factories:

C++
class CClassInstanceFactory
{
public:

    //Default constructor
    CClassInstanceFactory()
    {
    }

    //Default virtual destructor
    virtual ~CClassInstanceFactory()
    {
    }

    //override to return a class instance
    virtual void* Instance() = 0;

    //override to delete an instance of the same class
    virtual void Release(void* pInstance) = 0;
};

Now the class registry itself:

C++
//ClassReg.h

class CClassReg
{

    //Only one class registration table allowed
    DECLARE_SINGLETON( CClassReg );
    
protected:

    //A map from class identifiers to factories
    // for creating instances
    CTMap<mxPGUID, CClassInstanceFactory*> m_RegMap;
    typedef CTMap<mxPGUID, CClassInstanceFactory*>::TItem TItem;

public:

    //Register a class id along with its factory
    void Register(mxPGUID ClassID, CClassInstanceFactory* pFactory);

    //Remove a class id and its factory from the class registry
    void UnRegister(mxPGUID ClassID);

    //Return the factory for the given classID
    CClassInstanceFactory* GetFactory(mxPGUID ClassID);

};

//ClassReg.cpp

IMPLEMENT_SINGLETON( CClassReg );

CClassReg::CClassReg()
{
}

CClassReg::~CClassReg()
{
}

//Register a mapping between a class ID and a factory 
//for creating instances
void CClassReg::Register(mxPGUID ClassID, CClassInstanceFactory* pFactory)
{
    TItem item(ClassID, pFactory);
    m_RegMap.Append(item);
}

//Remove the mapping between a class ID and a facory
void CClassReg::UnRegister(mxPGUID ClassID)
{
    TItem item(ClassID, NULL);
    m_RegMap.RemoveAt(m_RegMap.Find(item));
}

//Get the factory for creating instances of a class by ID
CClassInstanceFactory* CClassReg::GetFactory(mxPGUID ClassID)
{
    TItem item(ClassID, NULL);
    return m_RegMap[m_RegMap.Find(item)].Second();
}

Note that the Class Registry itself is a Singleton.

Now, we can create a simple override of CClassInstanceFactory, which can be attached to something we want to create.

C++
template<class T>
class CTClassRegEntry : protected CClassInstanceFactory
{

public:

    //Default constructor registers class T
    CTClassRegEntry()
    {
        //Add T::ClassID to the reg table 
        //along with this factory that can create a T
        CTSingletonPtr<CClassReg> pReg;
        pReg->Register(T::ClassID(), this);        
    }

    //Default destructor unregisters class T
    ~CTClassRegEntry()
    {
        //Remove T::ClassID from the reg table 
        //along with this T factory
        CTSingletonPtr<CClassReg> pReg;        
        pReg->UnRegister(T::ClassID());        
    }

    //Return a typeless pointer to a new instance of T
     void* Instance()
    {
        return new T;
    }

    //Delete a T passed by typeless pointer
    virtual void Release(void* pInstance)
    {
        T* pT = reinterpret_cast<T*>(pInstance);
        delete pT;
    }

};

This class can be used directly as a class factory, or as a base class for deriving different kinds of factory.

In the sample code, you'll also find a CTInstancePtr<T> smart pointer class, very similar to CTSingletonPtr<T> described above. This can be used to get a pointer to an instance of any class that registers a CClassInstanceFactory derived factory with the CClassReg registry.

Runtime Abstract Factory

A class to implement the Abstract Factory Pattern in a way that means it can be configured at runtime:

C++
class CRTAbsFactory
{
protected:    

    //Map abstract class IDs to concrete class IDs
    CTMap<mxPGUID, mxPGUID> m_IDMap;
    typedef CTMap<mxPGUID, mxPGUID>::TItem TItem;

public:

    //Default constructor
    CRTAbsFactory()
    {
    }

    //Default virtual destructor
    virtual ~CRTAbsFactory()
    {
    }


    //Override an existing mapping and return 
    //the previous RHS element
    template<class Tsubs, class Torg>
    mxPGUID Override(Tsubs*, Torg*)
    {
        mxPGUID PrevClassID;
        mxPGUID BaseClassID = Torg::ClassID();
        mxPGUID SubClassID = Tsubs::ClassID();
        TItem item(BaseClassID, 0);
        TItem& Oitem = m_IDMap[m_IDMap.Find(item)];
        PrevClassID = Oitem.Second();
        Oitem.Second() = SubClassID;
        return PrevClassID;
    }

    //Override an existing mapping
    template<class Torg>
    void Override( mxPGUID ClassID, Torg* )
    {
        mxPGUID BaseClassID = Torg::ClassID();
        TItem item(BaseClassID, 0);
        TItem& Oitem = m_IDMap[m_IDMap.Find(item)];
        Oitem.Second() = ClassID;
    }

    //Configure a factory with a pair of classes
    template<class Tsubs, class Torg>
    void Configure(Tsubs*, Torg*)
    {
        //remember the id and then when asked for a T
        //create a class with id instead and cast to a T
        mxPGUID BaseClassID = Torg::ClassID();
        mxPGUID SubClassID = Tsubs::ClassID();
        TItem item(BaseClassID, SubClassID);
        m_IDMap.Append(item);
    }

    //Add a default T to T mapping to return
    // instances of the configured type
    void Configure( TItem item )
    {
        m_IDMap[m_IDMap.Find(item)].Second() = item.Second();
    }

    //Get the map element related to a specified type
    template<class Torg>
    TItem GetConfig(Torg*)
    {
        mxPGUID BaseClassID = Torg::ClassID();
        TItem item(BaseClassID, 0);
        return m_IDMap[m_IDMap.Find(item)];
    }

    //For requesting an instance of a class
    template<class T>
    void Instance(T*& pT)    
    {
        //Create a new class instance and set pT to point to it

        //Get a pointer to the class registry
        CTSingletonPtr<CClassReg> pReg;
        
        //Get the ID of the class being requested
        mxPGUID BaseClassID = T::ClassID();

        //look up associated concrete class ID
        TItem item(BaseClassID, 0);        
        mxPGUID SubClassID =  m_IDMap[m_IDMap.Find(item)].Second();        
        
        //Get an instance from the associated factory and return a base class 
        //pointer to that instance in pT
        pT = reinterpret_cast<T*>(pReg->GetFactory(SubClassID)->Instance());        
    }
};

By using a class ID to class ID map, we can get the factory to produce any subclass when Instance is called for the base class.

Prototype

The Prototype pattern is achieved by replacing the basic TClassRegEntry class with one that constructs all new instances by copying a prototype. Clearly, T must be copy constructable.

C++
template<class T>
class CProtoRegEntry : public CTClassRegEntry<T>
{
public:

    static T Prototype;//The prototype instance of class T

    //Default constructor
    CProtoRegEntry()
    {
    }

    //Default virtual destructor
    ~CProtoRegEntry()
    {
    }

    //Override of Instance to return a copy of the prototype
    void* Instance()
    {
        //Copy construct a T from the prototype instance
        return new T(Prototype);
    }
};

To make use of this, we need a few more macros:

C++
//Declare a class to be factory creatable
//from a prototype and create the factory and prototype
#define DECLARE_CLASS_PROTOTYPE(myClass)\
\
public:\
    DECLARE_OCLASS_ID(myClass);\
    static CProtoRegEntry< myClass > RegEntry

//Use PROTO(classname) to access the prototype instance of a class
#define PROTO(myClass) CProtoRegEntry< myClass >::Prototype

//Implementation to provide a factory configured 
//to create class instances from a prototype
#define IMPLEMENT_CLASS_PROTOTYPE(myClass)\
\
    IMPLEMENT_OCLASS_LUID(myClass);\
    CProtoRegEntry< myClass > myClass::RegEntry;\
    myClass PROTO(myClass)

//Add DECLARE_CLASS_PROTOTYPE(classname); to a class declaration and
//IMPLEMENT_CLASS_PROTOTYPE(classname); to the .cpp file to create and register a
//class instance factory object that can create instance of classname from a prototype
//Use PROTO(classname).membername = value; statements to set up the prototype

Pooled Objects

The approach used to achieve the Prototype pattern can also be used to implement Object Pooling. Many kinds of Object Pooling are possible. A very simplistic example is presented here.

C++
template<class T>
class CTClassPoolRegEntry : public CTClassRegEntry<T>
{
    friend T;    //Allow T to see and declare our internal CPoolEntry as a friend
            //so that CPoolEntry can create and delete T's
protected:

    //Internal class to represent an item in the Pool
    class CPoolEntry
    {
    public:

        T* m_pT;            //Pointer to a pooled class instance
        unsigned int m_uRefCount;    //Reference count on the instance

        //Default constructor 
        CPoolEntry()
        {
            //Pooled class may be expensive 
            //to create so we don't do it here
            m_pT = 0;                    
            m_uRefCount = 0;
        }

        //Copy construct from another PoolEntry
        CPoolEntry(const CPoolEntry& src)
        {
            m_pT = src.m_pT;
            m_uRefCount = 0;
        }

        //Default destructor frees the pooled instance
        ~CPoolEntry()
        {
            delete m_pT;
        }

        //Assign from another PoolEntry
        CPoolEntry& operator = (const CPoolEntry& src)
        {
            if(m_pT != src.m_pT)
            {
                delete m_pT;
                //Don't leak the pooled item we were referencing but don't     
                //delete it unless we would otherwise leak
            }
            m_pT = src.m_pT;
            m_uRefCount = src.m_uRefCount;
            return *this;
        }

        //Increment the reference count on the entry
        unsigned int AddRef()
        {
            return ++m_uRefCount;
        }

        //Decrement the reference count on the entry
        unsigned int Release()
        {
            return --m_uRefCount;
        }

        //return a pointer to the pooled class instance
        T* Item()
        {
            if(!m_pT)
            {
                m_pT = new T;//If we haven't got one yet create it now
            }
            return m_pT;
        }
    };

    CTArray<CPoolEntry> m_aPool;    //Array of items forming the pool
    int m_iReuse;            //Indicator to reuse the last released item

public:

    //Default constructor sets initial pool size
    CTClassPoolRegEntry(unsigned int uEstPoolSize) : CTClassRegEntry<T>()
    {
        m_aPool.SetCapacity(uEstPoolSize);
        m_iReuse = -1;
    }

    //Default destructor destroys all pool objects
    ~CTClassPoolRegEntry()
    {
        T* pT = NULL;
        unsigned int uCount;
        for(uCount = 0; uCount < static_cast<unsigned int>(m_aPool.Size()); uCount++)
        {
            pT = m_aPool[uCount].Item();
            delete pT;
        }        
        m_aPool.Free();
    }

    //Get an instance from the pool
    //This implementation prevents instance sharing but allows the pool to grow
    //Override it for limited, unlimited or algorithum 
    //determined instance numbers and sharing
    void* Instance()
    {
        T* pT = NULL;
        unsigned int uCount;

        //Find an instance not in use
        if(m_iReuse >= 0)    //If we know of an unused item use it
        {            
            pT = m_aPool[static_cast<unsigned int>(m_iReuse)].Item();
            m_iReuse = -1;
        }
        else            //Otherwise search the pool
        {
            for(uCount = 0; uCount < static_cast<unsigned int>(m_aPool.Size()); uCount++)
            {
                if(m_aPool[uCount].m_uRefCount == 0)
                {
                    m_aPool[uCount].AddRef()
                    pT = m_aPool[uCount].Item();
                    break;
                }
            }
        }

        //If all the instances are in use add a new one
        if(pT == NULL)
        {
            CPoolEntry entry;
            entry.m_uRefCount = 1;
            m_aPool.Append(entry);
            pT = m_aPool[m_aPool.Size() - 1].Item();
        }
        return pT;
    }

    //Release an instance to allow it to be reused
    void Release(void* pInstance)
    {
        unsigned int uCount;
        for(uCount = 0; uCount < static_cast<unsigned int>(m_aPool.Size()); uCount++)
        {
            if(m_aPool[uCount].m_pT == pInstance)
            {
                if(m_aPool[uCount].Release() <= 0)
                {
                    //Set the reuse indicator to the item no longer in use
                    m_iReuse = static_cast<int>(uCount);
                }
                break;
            }
        }
    }
};

Builder

The builder template presented is a very thin wrapper which abstracts the small common elements of Builder Pattern implementations.

C++
template< class TBuilder, class TProduct >
class CTBuilder : public TBuilder
{
public:

    CTBuilder()
    {
    }

    virtual ~CTBuilder()
    {
    }

    TProduct* Instance(void)
    {
        TProduct* pProduct = new TProduct;
        Construct( pProduct );
        return pProduct;
    }

    virtual void Release( TProduct* pInstance )
    {        
        delete pInstance;
    }
};

The TBuilder template parameter must be a concrete builder class with the relevant Construct function.

Flyer

The Flyer pattern, or Flyer instancing as I prefer to call it, is a new mode for the creation of and access to objects, which is made possible by the infrastructure I have described for implementing constructional patterns. Flyers are stack based objects, where the most recently created instance is always available to the current scope. They appear to fly up the stack without needing to be passed from function to function.

As a pattern, Flyer is a kind of per thread Singleton in that there is only ever one instance of any Flyer type accessible at any given point on any given thread.

Flyer requires a thread aware runtime class factory so that new instances can replace old ones at the top of a notional stack until they are destructed. This also allows 'overriding' of Flyers at runtime with derived classes.

First then, a Threaded Runtime Abstract Factory:

C++
class CThreadedRTAbsFactory
{
protected:    

    typedef CTMap<mxPGUID, mxPGUID> GUIDMap;
    typedef GUIDMap::TItem TGUIDItem;
    typedef CTMap<DWORD, GUIDMap >::TItem TThreadItem;

    //A map from Thread ID to GUIDMap
    CTMap<DWORD, GUIDMap > m_ThreadMap;    
    
    //Get the GUIDMap relevent to the calling thread
    GUIDMap& GetMap()
    {
        TThreadItem tItem;
        tItem.First() = ::GetCurrentThreadId();
        unsigned long ulFind = m_ThreadMap.Find(tItem);
        if( ulFind == - 1)
        {
            m_ThreadMap.Append(tItem);
            return m_ThreadMap[m_ThreadMap.Find(tItem)].Second();
        }
        else
        {
            return m_ThreadMap[ulFind].Second();
        }
    }

public:

    //Default constructor
    CThreadedRTAbsFactory()
    {
    }

    //Default virtual destructor
    virtual ~CThreadedRTAbsFactory()
    {
    }

    //Override which subclass is produced when 
    //a base is requested on the
    //calling thread
    template<class Tsubs, class Torg>
    mxPGUID Override(Tsubs*, Torg*)
    {
        //Get the map for the calling thread
        GUIDMap& IDMap = GetMap();

        mxPGUID PrevClassID;
        mxPGUID BaseClassID = Torg::ClassID();
        mxPGUID SubClassID = Tsubs::ClassID();
        TGUIDItem item(BaseClassID, 0);
        TGUIDItem& Oitem = IDMap[IDMap.Find(item)];
        PrevClassID = Oitem.Second();
        Oitem.Second() = SubClassID;

        //Return the id of the class we used to produce 
        //when Torg was requested
        return PrevClassID;
    }

    //Override the subclass produced by GUID
    template<class Torg>
    void Override( mxPGUID ClassID, Torg* )
    {
        //Get the map for the calling thread
        GUIDMap& IDMap = GetMap();

        mxPGUID BaseClassID = Torg::ClassID();
        TGUIDItem item(BaseClassID, 0);
        TGUIDItem& Oitem = IDMap[IDMap.Find(item)];
        Oitem.Second() = ClassID;
    }

    //Configure a factory with a pair of classes
    //Whenever a Torg is requested a Tsubs is created
    template<class Tsubs, class Torg>
    void Configure(Tsubs*, Torg*)
    {
        //Get the ma for the calling thread
        GUIDMap& IDMap = GetMap();

        mxPGUID BaseClassID = Torg::ClassID();
        mxPGUID SubClassID = Tsubs::ClassID();
        TGUIDItem item(BaseClassID, SubClassID);
        IDMap.Append(item);
    }

    //Configure a factory with a default T to T mapping
    void Configure( TGUIDItem item )
    {
        GUIDMap& IDMap = GetMap();
        IDMap[IDMap.Find(item)].Second() = item.Second();
    }

    //Get the ID of the product configured for Torg
    template<class Torg>
    TGUIDItem GetConfig(Torg*)
    {
        GUIDMap& IDMap = GetMap();
        mxPGUID BaseClassID = Torg::ClassID();
        TGUIDItem item(BaseClassID, 0);
        return IDMap[IDMap.Find(item)];
    }

    //Create a new class instance and set pT to point to it
    template<class T>
    void Instance(T*& pT)    
    {
        //Get the map for the calling thread
        GUIDMap& IDMap = GetMap();

        //Get a pointer to the class registry
        CTSingletonPtr<CClassReg> pReg;
        
        //Get the ID of the type requested
        mxPGUID BaseClassID = T::ClassID();

        //look up associated concrete class ID
        TGUIDItem item(BaseClassID, 0);        
        mxPGUID SubClassID =  IDMap[IDMap.Find(item)].Second();    
        
        //Get an instance from the associated factory
        // and return a pointer 
        //to the base class type in pT
        pT = reinterpret_cast<T*>(
        pReg->GetFactory(SubClassID)->Instance() );
    }
};

Now that we can configure a factory per thread, we need a Flyer factory template:

C++
template<class T>
class CTFlyerRegEntry : public CTClassRegEntry< T >
{
protected:
    static T* m_pInstance;

public:

    static inline bool Configure( T* pInstance )
    {
        if( pInstance != 0 )
        {
            pInstance->m_pPrevious = reinterpret_cast<T::_tBase*>(m_pInstance);
        }
        m_pInstance = pInstance;
        return true;
    }

    static inline bool Unconfigure( T* pInstance )
    {
        if( pInstance != 0 )
        {
            m_pInstance = reinterpret_cast<T*>(pInstance->m_pPrevious);
            return true;
        }
        return false;
    }

    //Return a 'typeless' pointer to a new instance of T
    virtual void* Instance()
    {
        return m_pInstance;
    }

    virtual void Release( void* )
    {        
    }

};

There are two ways we can use this to create a class with Flyer instancing. The MFC style method with a couple of macros, or the ATL style method using a templated base class.

The MFC-ish method needs these macros:

C++
#define DECLARE_CLASS_FLYER( _CLASS, _BASE )\
\
private:\
    friend class CTFlyerRegEntry< _CLASS >;\
\
protected:\
    typedef _BASE _tBase;\
    _BASE* m_pPrevious;\
    static CTFlyerRegEntry< _CLASS > RegEntry;\
public:\
    DECLARE_OCLASS_ID( _CLASS );\
    _CLASS()\
    {\
        RegEntry.Configure(this);\
    }\
    ~_CLASS()\
    {\
        RegEntry.Unconfigure(this);\
    }

#define IMPLEMENT_FLYER( _CLASS, _BASE )\
IMPLEMENT_OCLASS_LUID( _CLASS );\
CTFlyerRegEntry< _CLASS > _CLASS::RegEntry;\
_CLASS* CTFlyerRegEntry< _CLASS >::m_pInstance = 0

A class that uses these might look like this:

C++
//DemoFlyer.h
class CDemoFlyer
{
public:

    DECLARE_CLASS_FLYER( CDemoFlyer, CDemoFlyerBase );

    void SetValue( double dValue )
    {
        m_dValue = dValue;
    }

    double GetValue( void )
    {
        return m_dValue;
    }

private:

    double m_dValue;

};

//DemoFlyer.cpp
IMPLEMENT_FLYER( CDemoFlyer, CDemoFlyerBase );

If the macro magic is just too much for you, or it happens to be more to your taste, you might prefer an ATL-ish implementation. This requires an intermediate base class CTFlyer<T>.

C++
//An ATL style template base to make creating Flyers easier
template< class T, class Base >
class CTFlyer
{
protected:

    //Give the registry entry access to the protected members here
    friend class CTFlyerRegEntry< T >;
    
    //The registry entry needs to be able to determine the Base type
    //so give it a typedef it can access
    typedef Base _tBase;

    //Every Flyer instance must record which instance it replaces
    Base* m_pPrevious;

    //The one Registry entry for all Flyers of this type
    static CTFlyerRegEntry< T > RegEntry;

public:
    
    CTFlyer()
    {
        //When a Flyer is constructed configure the 
        //Registry entry to return this one as the
        //instance
        //Think of this as a Push
        RegEntry.Configure( (T*)this );
    }

    ~CTFlyer()
    {
        //When a Flyer is destructed configure the
        //Registry entry to return the previous
        //flyer instance
        //Think of the as a Pop
        RegEntry.Unconfigure( (T*)this );
    }

};

Now, we just need to derive a class from CTFlyer and give it an ID to make it a Flyer. What could be easier?

C++
//DemoFlyer.h
class CDemoFlyer : public CTFlyer< CDemoFlyer, CDemoFlyerBase >
{
    
public:

    DECLARE_OCLASS_ID( CDemoFlyer );

    void SetValue( double dValue )
    {
        m_dValue = dValue;
    }

    double GetValue( void )
    {
        return m_dValue;
    }

private:

    double m_dValue;
};

//DemoFlyer.cpp
IMPLEMENT_CLASS_FLYER( CDemoFlyer, CDemoFlyerBase );

Demo Code

The demo code is a VC8 Console App solution which is indented to be run in Debug within the Visual Studio IDE. It provides a very simple functional demo of each of the creational patterns. It is designed to be as self contained as possible, implementing its own data structures. For most, this probably isn't the way you'd want to use the significant classes, but it does make it clear exactly what's required to make each class work.

Conclusion

Beating these templates into shape for publication has been interesting. It has made me refer back to the Patterns book and think of other ways I might make use of some of these classes to improve my code. Flyer, in particular, needed a surprising amount of work. It made much more sense in my head than on the screen. I hope I've changed that. Its usefulness may not at first be obvious. Just imagine a Flyer as something that gets passed with every function call made while it exists, but at no cost unless you actually use it. The downside is that when you do use it, it's not a particularly cheap call. In my opinion, this makes Flyer the perfect vehicle for an error handling mechanism. In my next article, I hope to demonstrate just that with a comprehensive Win32 error handling framework. As always, comments and suggestions are welcome. There is, I'm sure, much that could be improved.

Acknowledgements

This article was constructed with the assistance of:

Revision History

  • 14th July 2007: Original article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO Querysoft Ltd
United Kingdom United Kingdom
I'm a UK based software engineering contractor, CEO of Querysoft Ltd, a candidate and activist for the UK Independence Party and occasionally I get time look at Code Project.

Comments and Discussions

 
GeneralGood Pin
NormDroid18-Jul-07 21:03
professionalNormDroid18-Jul-07 21:03 
GeneralRe: Good Pin
Matthew Faithfull18-Jul-07 21:52
Matthew Faithfull18-Jul-07 21:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.