| Roger Onslow (view profile) April 16, 2001 |
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.
Let's take a simple class hierarchy:
(continued)
![]() |
![]() New products, new technologies: Microsoft SQL Server 2008, Microsoft Visual Studio 2008, and Windows Server 2008 create new opportunities to use your existing skills and to grow your business. ? ![]() You can grow your practice by increasing business with existing clients or adding more customers, expanding your practice areas or expanding your business into hosted or managed services. Discover which approach to growing your business makes the most sense for you and your customers. ? ![]() Explore the business opportunities available through the Microsoft Partner Program, which is designed to help you generate leads, drive customer demand and sales, increase your profitability, and assess your business performance. ? ![]() Heres a great way to build your business: Team up with partners whose solutions and services work as an extension of your own offerings. View this Webcast to learn how building networks with other partners can help your business grow. ? ![]() Knight Enterprises Inc., a longtime Novell partner, chose to add Microsoft technologies to its offerings after attending a Microsoft partner event. The company's major accounts soon followed suit, opting for Microsoft solutions over those of the competition. Download this case study to discover how Knight now expects its revenue to double in two years time. ? |
![]() |


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 functionvirtual 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.
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 theseclass CWnd {};class CListCtrl : public CWnd {};class CTreeCtrl : public CWnd {};// my classesclass 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 theseclass CWnd {};class CListCtrl : public CWnd {};class CTreeCtrl : public CWnd {};// new behaviourclass CAddBehaviour {public:virtual void NewFunction();};// my classesclass 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 CMyListCtrlCMyListCtrl(): 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 CMyListCtrlCMyListCtrl(): Base(), CAddBehaviour(this) {// body of constructor}};// similarly for CMyTreeControl
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 CMyListCtrlCMyListCtrl(): Base(), CAddBehaviour(this) {// body of constructor}protected://{{AFX_MSG(CMyListCtrl)afx_msg void OnSomeMe ssage();//}}AFX_MSGDECLARE_MESSAGE_MAP()};BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)//{{AFX_MSG_MAP(CMyListCtrl)ON_COMMAND(ID_SOMETHING,OnSomeMessage);//}}AFX_MSG_MAPEND_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.
// 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.
// 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 TAddBehaviourTAddBehaviour (): BASE(), CAddBehaviour(this) {// body of constructor}protected://{{AFX_MSG(TAddBehaviour)afx_msg void OnSomeMessage();//}}AFX_MSGDECLARE_MESSAGE_MAP()};BEGIN_MESSAGE_MAP(TAddBehaviour, BASE)//{{AFX_MSG_MAP(TAddBehaviour)ON_COMMAND(ID_SOMETHING,OnSomeMessage);//}}AFX_MSG_MAPEND_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 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.
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 staticD:\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.
| Add www.codeguru.com to your favorites ![]() ![]() ![]() ![]() ![]() ![]() |
聯(lián)系客服