Click here to Skip to main content
15,885,107 members
Articles / Programming Languages / C++
Article

Enumerate your leaf classes

Rate me:
Please Sign up or sign in to vote.
4.63/5 (8 votes)
4 Jan 2004CPOL6 min read 57.3K   1.7K   21   8
An article on a set of macros and a factory class to enumerate and dynamicaly create leaf classes derived from virtual base classes

Image 1

Introduction

MFC gives you a beautiful framework to dynamically create classes as long as you derive them from CObject. In a project much like the ColorFilter demo I'm using as an example here, I was developing, the user had to be able to select one out of several derived class. As the project evolved the number of these derived (leaf) classes increased and I found that I had to make changes to the program at various points and inevitably always forgot to make one of them. Using CObject and MFC's runtime class information seemed at first a possible solution but unfortunately this framework does not let you differentiate between leaf classes derived from different (virtual) base classes as CObject is the base class for all of them. Moreover you must include MFC in your project. I have therefore developed a set of macro's and a templated factory class that allows the most derived class (i.e. the leaf) to register itself with the base class from which it is derived. All leaf classes can thus be enumerated and created through the base class.

The demonstration project shows one (very simplistic) way in which enumeration of leaf classes can be used. A number of color filters are derived from a base filter class. Each leaf class is automatically added to the menu bar where the user can select it. Upon selection of a particular color filter, the filter is created and applied to a circular section of the displayed bitmap.

The real benefit from this enumeration capability lies in the fact that hardcoded knowledge of each leaf class becomes unnecessary. If you want to add another filter you don't need to add another menu handler or update a switch statement.

Background

Virtual base classes are regularly used as abstract interfaces. A pointer to the base class is sufficient to call functions in the leaf class.

class CLeaf : public CBase
{
};

SomeGenericFunction(CBase* pBase)
{
    pBase->WhatEverNeedsToBeDone();
}

CBase* pBase = new CLeaf;
SomeGenericFunction(pBase);
As this little sample shows, hardcoded knowledge of derived classes is necessary (new CLeaf). If you derived a dozen classes from the same base class and you wanted to instantiate one out of twelve, you're first approach would probably be code like this:
CBase* pBase = NULL;
switch(nDesiredLeafClass)
{
    case 0: pBase = new CLeaf_0; break;
    case 1: pBase = new CLeaf_1; break;
    case 2: pBase = new CLeaf_2; break;
    .
    .
    .
}
SomeGenericFunction(pBase);
Adding leaf class number 13 now becomes a real headache. You have to remember to update the switch statement somewhere in your code. Do you always remember how many you've implemented and in which files?. If you want to write solid code you should only have to make changes in one place.

Using the code

All the necessary code to implement enumerating leaf classes with factories is contained in one header file: BootStrap.h. Include this file in your project and add the macro calls to your base and leaf classes. The DECLARE_LEAF_CLASS() and IMPLEMENT_LEAF_CLASS() only need to be added to the most derived class.

Declaration of the base class in the demo application:

#include "BootStrap.h"

class CBaseFilter  
{
DECLARE_ROOT_CLASS(CBaseFilter)
public:
    void             Apply(...);
    virtual COLORREF ChangeColor(...) = 0;
    .
    .
    .
};

And the implementation of this base class:

IMPLEMENT_ROOT_CLASS(CBaseFilter)

void CBaseFilter::Apply(...)
{
   .
   .
   .
   //Call virtual function in leaf class
   COLORREF cr = ChangeColor(crOldPixelColor);
   .
   .
   .
}

The IMPLEMENT_ROOT_CLASS(CBaseFilter) macro adds two public, static functions to your base class. They are:

static int                        GetRegisteredManufactoringPlantCount();
static CBootStrapper<base_class>* GetRegisteredManufactoringPlant(int nIndex);

The first function lets you find out how many leaf classes are derived from the base class and with the second you obtain a pointer to the factory for each leaf class.

A very simple class derived from this base class is declared like this:

#include "BaseFilter.h"

class CBlueFilter : public CBaseFilter  
{
DECLARE_LEAF_CLASS(CBaseFilter)
public:
   CBlueFilter();
   virtual ~CBlueFilter();

   virtual COLORREF    ChangeColor(COLORREF crPure);
};

and implemented like this:

IMPLEMENT_LEAF_CLASS(CBlueFilter, CBaseFilter, _T("Blue Filter"))

CBlueFilter::CBlueFilter(){}
CBlueFilter::~CBlueFilter(){}

COLORREF CBlueFilter::ChangeColor(COLORREF crPure)
{
   COLORREF crNew = RGB(0, 0, GetBValue(crPure));
   return crNew;
}

As you can see there is very little work needed to add this functionality to a class structure. And because all the necessary changes to another flavor of leaf class can be kept local to one file and one section of code, there is very little danger in copy and pasting.

The first example shows how to use the added functionality to add the names of the leaf classes to a menu item in the mainframe's menu bar. This is done using the GetClassName() function in the CBootStrapper class.

BOOL CChildView::Create(LPCTSTR lpszClassName, ...) 
{
   .
   .
   .
   // Get the main window's menu
   CMenu* pMenu = pMain->GetMenu();

   //The "Filter" menu should be the second item on the menu bar.
   CMenu* pSubMenu = pMenu->GetSubMenu(1);
   if(NULL != pSubMenu)
   {
      //Append our color filter menu items to the tail of this menu
      CBaseFilter* pBase   = NULL;
      int          nLeaves = pBase->GetRegisteredManufactoringPlantCount();
      UINT         nMenuID = ID_FILTER_NONE + 1;

    //Iterate through the vector of Leaf classes 
    //and add their names as menu items
      for(int nIdx = 0; nIdx < nLeaves; nIdx++, nMenuID++)
      {
         CBootStrapper<CBaseFilter>* pBoot = 
               pBase->GetRegisteredManufactoringPlant(nIdx);
         pSubMenu->AppendMenu(MF_STRING | MF_ENABLED, 
              nMenuID, pBoot->GetClassName());
      }
      // force a redraw of the menu bar
      pMain->DrawMenuBar();
   }
   return TRUE;
}

The second example shows how to create an instance of a leaf class using the CreateObject() function in the CBootStrapper class.

BOOL CChildView::OnCmdMsg(UINT nID, int nCode, void* pExtra, 
    AFX_CMDHANDLERINFO* pHandlerInfo) 
{
   UINT nFirstID = ID_FILTER_NONE;
   UINT nLastID  = nFirstID + 
      CBaseFilter::GetRegisteredManufactoringPlantCount();

   //Let the base class handle this command message 
   //if it is not in our range 
   if( (nID < nFirstID) || (nID > nLastID))
   {
      return CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
   }

   //handle a menu command if it is in our ID range and 
   //if it is not a CmdUI message
   if(NULL == pHandlerInfo && CN_COMMAND == nCode)
   {
      //Handle one of our added menu items!!!
      if(nID == nFirstID)
      {
         //No filtering
         m_dcFiltered.BitBlt(0, 0, m_bmp.bmWidth, 
               m_bmp.bmHeight, &m_dcSource, 0, 0, SRCCOPY);
      }
      else
      {
         BeginWaitCursor();

         //Determine the requested filter from the command ID
         int nFilterIndex = nID - nFirstID - 1;
//--- 
         CBootStrapper<CBaseFilter>* pBoot = 
             CBaseFilter::GetRegisteredManufactoringPlant(nFilterIndex);
         //Create an instance of the requested filter
         CBaseFilter* pFilter = pBoot->CreateObject();
         //Do whatever needs to be done
         pFilter->Apply(&m_bmp, &m_dcSource, &m_dcFiltered);
         //And destroy the filter object we've just created
         delete pFilter;
//---
         EndWaitCursor();
      }
      RedrawWindow();
   }

   return TRUE;
}

Points of Interest

Static variables are constructed before any user code is executed thus while the IMPLEMENT_LEAF_CLASS() macro is relying on the IMPLEMENT_ROOT_CLASS(xx) having been executed there is no obvious way this can be enforced in user code. And according to the MSDN library the only guarantee you get is that static objects are constructed in the order in which they appear in the source file, but that no guarantee can be given as to the order in which the source files are linked! Therefore it would seem that all the static objects in the base class and its derived classes should be in the same source file so that their order would ensure proper initialization. A very annoying situation because that prevents the use of individual implementation files for base class and derived class.

At first I hoped to solve this problem by concentrating the implementation of static code in templated functions, the leaf class then only has to derive itself from the base class and the template class. A bug in the VC++ 6.0 linker prevents this because it can not find the code for a static function in a template class (Error: LNK2001). My second thought was to put some compiler directives in the macro functions warning if the IMPLEMENT_LEAF_CLASS(xx) did not appear in the same file as the IMPLEMENT_ROOT_CLASS(xx) macro. Well, compiler directives are not allowed inside macro functions (who'd have thunk! so many silly bugs could be prevented by a few simple macro's.).

Going through the MSDN database for a possible solution, I came across the #pragma init_seg() compiler directive. With it you can force the compiler to put the constructors (and destructors of course) of your static objects in special initialization segments. Objects in the "compiler" segment are constructed before objects in the "lib" segment which are constructed before code in the "user" segment. By default, the code you write is placed in the "user" segment. Thus by placing the constructor of the factory vector in the base class in the "lib" segment, it is assured to have been initialized before the constructors of the factories in the leaf classes add themselves to this vector!

The #pragma init_seg() works as advertised and I used it in the first version of the macro set. But as PeterH quite rightly pointed out, compiler directives are not portable. And besides all my efforts to keep everything in single statements, the need to add this statement and the absence of anything to force its presence or detect its absence still didn't make it foolproof. So based upon PeterH's comment I investigated the possibility to use the C++ standard to beat this problem. Now a little silly looking statement in the IMPLEMENT_ROOT_CLASS(xx) macro forces the compiler to create the static vector in the base class the first time a leaf class registers its static factory class.

unsigned int base_class::RegisterManufactoringPlant(
    CBootStrapper<base_class>* pLeaf)
{                    
/*!!*/
  static bool blCreate = (NULL == 
    (m_vecRegisteredLeafManufactoringPlants = 
     new std::vector<CBootStrapper<base_class>*>() )); 
  m_vecRegisteredLeafManufactoringPlants->push_back(pLeaf);    
    return GetRegisteredManufactoringPlantCount();  
}  

History

  • Created around the end of the summer of '03
  • Improved upon suggestions by PeterH in December '03

License

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


Written By
Software Developer (Senior)
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generaluse in an external class library Pin
gutchess27-Jul-04 5:55
gutchess27-Jul-04 5:55 
GeneralRe: use in an external class library Pin
Jaded Hobo31-Jul-04 8:01
Jaded Hobo31-Jul-04 8:01 
GeneralPoints of Interest... Pin
PeteH3-Nov-03 0:26
PeteH3-Nov-03 0:26 
GeneralRe: Points of Interest... Pin
Jaded Hobo21-Dec-03 9:37
Jaded Hobo21-Dec-03 9:37 
GeneralThat's very nice Pin
Nicolas Bonamy26-Sep-03 22:20
Nicolas Bonamy26-Sep-03 22:20 
GeneralYour code has already seen active service... Pin
Iain Clarke, Warrior Programmer25-Sep-03 5:30
Iain Clarke, Warrior Programmer25-Sep-03 5:30 
You "enumerate child classes" code has just solved a problem I was bumping against yesterday.
While I could have solved it another way (I have in the past) your version is much neater than
mine!

Thanks,

Iain.

GeneralRe: Your code has already seen active service... Pin
Chris Maunder25-Sep-03 6:53
cofounderChris Maunder25-Sep-03 6:53 
GeneralRe: Your code has already seen active service... Pin
Jaded Hobo25-Sep-03 11:59
Jaded Hobo25-Sep-03 11:59 

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.