First, let’s consider how one can go about adding new functionality to a group of classes. The most obvious, brute-force way to add the same functionality to a number of classes is to simple copy and paste the code into each one. This is far from an ideal solution- you only want to write the code once and have a single copy of it to maintain.
Modifying the Base Class
If we were creating our own class hierarchy and wanted to add some functionality to all of our derived classes, then the obvious place to add it is the base class. If the details of how to implement the behaviour need to be different for the derived classes, then we would use virtual functions to override the required implementation.
Let’s take a simple class hierarchy:
This gives you pseudocode that is something like this:
class Base {}; class Derived1 : public Base {}; class Derived2 : public Base {};
Add a virtual NewFunction() to the base class:
class Base { public: virtual void NewFunction(); }; class Derived1 : public Base { // no change, just gets Base::NewFunction() }; class Derived2 : public Base { public: // override the new member function virtual void NewFunction(); };
NewFunction() is now available in both class Derived1 and class Derived2. Furthermore, the implementation of NewFunction() has been overridden for class Derived2.
This is fine if we are writing the entire class hierarchy ourselves and can change the source code as we see fit. However, when it comes to MFC, we are working with an existing class library. We cannot go adding new member functions to the CWnd base class. We cannot even change the other classes in the existing hierarchy. And that means this approach will not work.
Multiple Inheritance
Another way of adding functionality is to use multiple inheritance so we can add functionality to derived classes without modifying the base class. We put the new functionality into a separate base class and then change the declarations of the derived classes so they derive from this new class.
To make things more familiar, I will use MFC class names in the diagrams and code snippets. The base class will be CWnd, and our two derived classes CListCtrl and CTreeCtrl. We cannot change any of these classes directly, as they are part of the MFC class library. However, we can derive our own classes from them as shown:
This gives you code that is something like this:
// library classes .. we don't touch these class CWnd {}; class CListCtrl : public CWnd {}; class CTreeCtrl : public CWnd {}; // my classes class CMyListCtrl : public CListCtrl {}; class CMyTreeCtrl : public CTreeCtrl {};
To add extra functionality, we create a new class called CAddBehaviour and use multipleinheritance for our own derived classes. We do not need to make any changes to the library classes.
// library classes .. we don't touch these class CWnd {}; class CListCtrl : public CWnd {}; class CTreeCtrl : public CWnd {}; // new behaviour class CAddBehaviour { public: virtual void NewFunction(); }; // my classes class CMyListCtrl : public CListCtrl, public CAddBehaviour {}; class CMyTreeCtrl : public CTreeCtrl, public CAddBehaviour { public: virtual void NewFunction(); };
There is a slight catch here, however. If the implementation of CAddBehaviour::NewFunction() requires access to the members of CWnd–to do anything useful, it probably will–then we are out of luck as things stand because we cannot derive CAddBehaviour from CWnd. There is a trick we can use here to help get around this obstacle. We can store a pointer to a CWnd object in the CAddBehaviour class.
class CAddBehaviour { public: CAddBehaviour(CWnd* pWnd) : m_pWnd(pWnd) { // body of constructor } virtual void NewFunction(); protected: CWnd* m_pWnd; }; class CMyListCtrl : public CListCtrl, public CAddBehaviour { public: // change the constructor for CMyListCtrl CMyListCtrl() : Base() , CAddBehaviour(this) { // body of constructor } }; // similarly for CMyTreeControl
Let’s look at how a CMyListCtrl object would be laid out in memory: In addition to the specific data for CMyListCtrl, there are a CListCtrl part (which includes the CWnd base class) and a CAddBehaviour part.
When an object of CMyListCtrl is constructed, both its CListCtrl part and its CAddBehaviour part are also constructed. The constructor initialises the pointer in the CAddBehaviour part to point to itself. In particular, to its CWnd part. This means that CAddBehaviour::NewFunction() can use m_pWnd to call public members of CWnd. The only remaining catch is that NewFunction() can only access public members of CWnd. If we could change CWnd and declare CAddBehaviour to be a friend, then all would be well; we can’t do that because the whole reason for using multiple inheritance in the first place is so that we do not need to change the base CWnd class. But, as always, there is a solution: Derive a class from CWnd that does have CAddBehaviour as a friend. Then we can use that to get at protected members of CWnd. That would make our code look like this: |
class CAddBehaviourFriend : public CWnd { friend class CAddBehaviour; }; class CAddBehaviour { public: CAddBehaviour(CWnd* pWnd) : m_pWnd((CAddBehaviourFriend*)pWnd) { // body of constructor } virtual void NewFunction(); protected: CAddBehaviourFriend* m_pWnd; }; class CMyListCtrl : public CListCtrl, public CAddBehaviour { public: // change the constructor for CMyListCtrl CMyListCtrl() : Base() , CAddBehaviour(this) { // body of constructor } }; // similarly for CMyTreeControl
Message Maps
Well, this seems like a reasonable solution. With minimal change we can add new functionality to any or all of our derived classes. But all is still not well. MFC throws a big spanner in the works with Message Maps. Now, don’t get me wrong, Message Maps are wonderful things, but there are some limitations. There can be only a single message map at each level of the hierarchy, and the functions called and the class that owns the message map must have CWnd ancestry. That means we cannot put up a message map in our CAddBehaviour class. Nor can we put a message map in the CMyListCtrl class and have it directly call member functions of CAddBehaviour.
Well, this is fairly easy to get around. We put the message map in CMyListCtrl class and call member functions of CmyListCtrl, which in turn call member functions of CAddBehaviour such as NewFunction(). This would mean we have code something like this:
class CMyListCtrl : public CListCtrl, public CAddBehaviour { public: // change the constructor for CMyListCtrl CMyListCtrl() : Base() , CAddBehaviour(this) { // body of constructor } protected: //{{AFX_MSG(CMyListCtrl) afx_msg void OnSomeMe ssage(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl) //{{AFX_MSG_MAP(CMyListCtrl) ON_COMMAND(ID_SOMETHING,OnSomeMessage); //}}AFX_MSG_MAP END_MESSAGE_MAP(); void CMyListCtrl::OnSomeMessage() { NewFunction(); } // similarly for CMyTreeControl
The only problem now is that we now need to add multiple inheritance, a new constructor, a message map, and some wrapper functions that call CAddBehaviour for every class that we want to have our new behaviour. This is almost as much work to get right as manually adding the behaviour to each class individually. So, it doesn’t look like all this has helped much.
Template Classes
There is another method of adding behaviour that doesn’t involve multiple inheritance. In fact it is very much like manually adding code to each class. This method is templates. To add functionality using a template, one usually slots a template class between a base class and a derived class. The template class has a template parameter that specifies a base class and adds extra functionality to it. You then derive your class from an instance of the template class with the appropriate base class filled in. This is somewhat different from multiple inheritance, as shown below:
// Multiple-inheritance... class Base {}; class Extra { public: virtual void NewFunction(); }; class Derived : public Base, public Extra { // gets Extra::NewFunction() }; // Template class... class Base {}; template <class BASE> class Extra : public BASE { public: virtual void NewFunction(); }; class Derived : public Extra<Base> { // gets Extra<Base>::NewFunction() };
This is illustrated in the class diagram below:
One problem with templates is that their implementation usually has to be exposed to the world with inline member function definitions. Also, you end up with separate instances of each function for every different base class you use, which can result in code bloat.
The Best of Both Worlds
One can combine the shared code and encapsulation of multiple inheritance with the ease of use of templates to get a good solution with the advantages of both methods.
// Multiple-inheritance... class Base {}; class ExtraBase { public: virtual void NewFunction(); }; template <class BASE> class Extra : public BASE, public ExtraBase { }; class Derived : public Extra<Base> { // gets Extra<Base>::NewFunction() };
This is illustrated in the class diagram below:
By using the combination of multiple inheritance and templates we should be able to improve on our previous solution. The template class can do the work of adding multiple inheritance, fixing constructors, and adding wrapper functions and message maps for us so we don’t need to do this for every derived class.
This means we end up with code something like this:
template <class BASE> class TAddBehaviour : public BASE, public CAddBehaviour { public: // change the constructor for TAddBehaviour TAddBehaviour () : BASE() , CAddBehaviour(this) { // body of constructor } protected: //{{AFX_MSG(TAddBehaviour) afx_msg void OnSomeMessage(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; BEGIN_MESSAGE_MAP(TAddBehaviour, BASE) //{{AFX_MSG_MAP(TAddBehaviour) ON_COMMAND(ID_SOMETHING,OnSomeMessage); //}}AFX_MSG_MAP END_MESSAGE_MAP(); void TAddBehaviour::OnSomeMessage() { NewFunction(); } class CMyListCtrl : public TAddBehaviour<CListCtrl> { }; // similarly for CMyTreeControl
You can see that the template class now does all the work of stitching in the new behaviour for us. All we have to do is derive from it. Problem solved.
The Problem with Message Maps
Well, not quite.
The problem would be solved if the MFC message map macros worked with template classes. Unfortunately they don’t. In particular, BEGIN_MESSAGE_MAP just will not work with template classes. The reason is that this macro actually defines and initialises a couple of static members and functions. Syntactically, each of the definitions needs to be preceded by ‘template
Here are the definitions for the message map macros:
#ifdef _AFXDLL #define DECLARE_MESSAGE_MAP() \ private: \ static const AFX_MSGMAP_ENTRY _messageEntries[]; \ protected: \ static AFX_DATA const AFX_MSGMAP messageMap; \ static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); \ virtual const AFX_MSGMAP* GetMessageMap() const; \ #else #define DECLARE_MESSAGE_MAP() \ private: \ static const AFX_MSGMAP_ENTRY _messageEntries[]; \ protected: \ static AFX_DATA const AFX_MSGMAP messageMap; \ virtual const AFX_MSGMAP* GetMessageMap() const; \ #endif #ifdef _AFXDLL #define BEGIN_MESSAGE_MAP(theClass, baseClass) \ const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \ { return &baseClass::messageMap; } \ const AFX_MSGMAP* theClass::GetMessageMap() const \ { return &theClass::messageMap; } \ AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \ { &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \ AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \ { \ #else #define BEGIN_MESSAGE_MAP(theClass, baseClass) \ const AFX_MSGMAP* theClass::GetMessageMap() const \ { return &theClass::messageMap; } \ AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \ { &baseClass::messageMap, &theClass::_messageEntries[0] }; \ AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \ { \ #endif #define END_MESSAGE_MAP() \ {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ }; \
Notice that the macros are slightly different depending on whether or not they are being used within a MFC Extension DLL.
The DECLARE_MESSAGE_MAP and END_MESSAGE_MAP are both fine as they are. DECLARE_MESSAGE_MAP is used within the class declaration itself, and so doesn’t need ‘template
So we only need to make a special version of BEGIN_MESSAGE_MAP macro that will include the required template syntax. This is pretty straightforward:
#ifdef _AFXDLL #define BEGIN_MESSAGE_MAP_FOR_TEMPLATE(theClass, baseClass) \ template <class baseClass> const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \ { return &baseClass::messageMap; } \ template <class baseClass> const AFX_MSGMAP* theClass::GetMessageMap() const \ { return &theClass::messageMap; } \ template <class baseClass> AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \ { &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \ template <class baseClass> AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \ { \ #else #define BEGIN_MESSAGE_MAP_FOR_TEMPLATE(theClass, baseClass) \ template <class baseClass> const AFX_MSGMAP* theClass::GetMessageMap() const \ { return &theClass::messageMap; } \ template <class baseClass> AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \ { &baseClass::messageMap, &theClass::_messageEntries[0] }; \ template <class baseClass> AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \ { \ #endif
That should do it we can now use BEGIN_MESSAGE_MAP_FOR_TEMPLATE when we define the message map and all should work just fine.
The Bug!
That is, if it were not for an unexpected error message. Please note that I use VC6.0 SP3 and build with the error-level set to level 4; I don’t let my code build with even warning messages.
When I first built sample code that used this special version of the message map macro I got the following message within the macro line:
warning C4211: nonstandard extension used : redefined extern to static
Hmmm… what’s going on here? Well, for a start, because I am using a macro, I cannot see the exact line of the macro that is causing the problem. So, the first step in such a case is to expand the macro by hand and use that instead of the macro call.
OK. I did that.It is not too hard when you use find and replace in the text editor. So I tried compiling again to see if it was more obvious where the error is. This time I see that the error is in the definition of theClass::messageMap. That is, the following line from the original macro:
template <class baseClass> AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \ { &baseClass::messageMap, &theClass::_messageEntries[0] }; \
Well, no matter how many times I look at that code, I just cannot see anything wrong with it.
Time for the next step: Try to reproduce the problem in a simpler context and cut it down until I get the simplest code possible that still exhibits the bug. This is pretty much a binary search process. I start with the existing code and remove what doesn’t look like it would affect the bug, one at a time. After each step, I recompile and verify that either the bug is still there or has disappeared. If it is still there, I carry on, otherwise I need to reinstate the section of code I removed and either try something else or split the removal into smaller steps. Some of the things that I tried first include removing the AFX_COMDAT and AFX_DATADEF macros (they made no difference); I also changed from the more complicated AFX_MSGMAP structure to just using a simple ‘int’. Still the bug persisted.
I won’t bore you (any further) with all the intermediate steps and false trails, but instead show you the code that demonstrates when the error happens and when it doesn’t.
#include "stdafx.h" template <class T> class X1 { /* virtual */ const int* f() const { return &i; } static const int i; }; template <class T> const int X1<T>::i = 1; X1<double> x1; template <class T> class X2 { virtual const int* f() const { return &i; } static /* const */ int i; }; template <class T> int X2<T>::i = 2; X2<double> x2; template <class T> class X3 { virtual const int* f() const { return &i; } static const int i; }; template <class T> const int X3<T>::i = 3; X3<double> x3; class X4 { virtual const int* f() const { return &i; } static const int i; }; const int X4::i = 3; X4 x4; int main() { return 0; }
Look at the three template classes: X1, X2, and X3. X1 and X2 are both similar to X3, except that X1 makes function f() nonvirtual and X2 makes the static data value non-const. X4 is a non-template version. X1, X2 and X4 all compile just fine. However, X3 gives the same error message:
D:\SOURCE\TEST\Test.cpp(21) : warning C4211: nonstandard extension used : redefined extern to static D:\SOURCE\TEST \Test.cpp(11) : while compiling class-template static data member 'const int X3::i'
This error message seems to make no sense at all. It seems that the compiler gets confused with a combination of template classes, virtual function and static const data. In other words, it is a compiler bug!
Well, I always have mixed feelings about compiler bugs. I get some satisfaction from knowing that the programmers at Microsoft are just as human as the rest of us and I feel relieved that it wasn’t something I did wrong after all (this time). On the other hand, I’m annoyed that I had to spend so much time tracking down an error that was someone else’s fault. And I then thinkhow am I going to work around it?
Well, in this case there is a fairly simple solution. Although I cannot make the functions non-virtual function, I can get rid of the const-ness of the static data. To do this, I need to change the definition of my special version of BEGINE_MESSAGE_MAP as well as DECLARE_MESSAGE_MAP (where the static data is declared). While I’m at it, I’ll also make an exact copy of the END_MESSAGE_MAP macro so the names are all consistent.
This is what I ended up with:
#ifndef _MESSAGEMAPSFORTEMPLATES_ #define _MESSAGEMAPSFORTEMPLATES_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #ifdef _AFXDLL #define DECLARE_MESSAGE_MAP_FOR_TEMPLATE() \ private: \ static /*const*/ AFX_MSGMAP_ENTRY _messageEntries[]; \ protected: \ static AFX_DATA /*const*/ AFX_MSGMAP messageMap; \ static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); \ virtual const AFX_MSGMAP* GetMessageMap() const; \ #else #define DECLARE_MESSAGE_MAP_FOR_TEMPLATE() \ private: \ static /*const*/ AFX_MSGMAP_ENTRY _messageEntries[]; \ protected: \ static AFX_DATA /*const*/ AFX_MSGMAP messageMap; \ virtual const AFX_MSGMAP* GetMessageMap() const; \ #endif #ifdef _AFXDLL #define BEGIN_MESSAGE_MAP_FOR_TEMPLATE(theClass, baseClass) \ template <class baseClass> const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \ { return &baseClass::messageMap; } \ template <class baseClass> const AFX_MSGMAP* theClass::GetMessageMap() const \ { return &theClass::messageMap; } \ template <class baseClass> AFX_COMDAT AFX_DATADEF /*const*/ AFX_MSGMAP theClass::messageMap = \ { &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \ template <class baseClass> AFX_COMDAT /*const*/ AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \ { \ #else #define BEGIN_MESSAGE_MAP_FOR_TEMPLATE(theClass, baseClass) \ template <class baseClass> const AFX_MSGMAP* theClass::GetMessageMap() const \ { return &theClass::messageMap; } \ template <class baseClass> AFX_COMDAT AFX_DATADEF /*const*/ AFX_MSGMAP theClass::messageMap = \ { &baseClass::messageMap, &theClass::_messageEntries[0] }; \ template <class baseClass> AFX_COMDAT /*const*/ AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \ { \ #endif #define END_MESSAGE_MAP_FOR_TEMPLATE() END_MESSAGE_MAP() #endif
I can now use these macros as direct replacements for the original macros when I write a template class.
Phew!
In the next instalment of my series of articles, I’ll combine the custom draw class from the last article with the techniques and bug work-arounds of this article to come up with a set of classes that helps with custom draw for common control.