Adding Behavior to Classes, Part I - An Introduction

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.

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:

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.

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:

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:

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:

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.

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.

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 ', but they are not, because the macro is written to work with nontemplate classes.

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 ' prefixed. END_MESSAGE_MAP is also fine since it just adds the last line of initialisation to the message map data.

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:

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:

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.

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:

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.

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.

Comments

helped me alot

no compiler bug in VC6 SP6 ;)

Posted by Oliver Twesten
on 05/04/2004 03:54pm

Nice article, good design idea I never thought of (using template class and multiple inheritance).
The compiler bug seems to be fixed in VC6 SP6 - at least the code snipped compiles without warnings/errors.
Thank you!
Oliver.

Top White Papers and Webcasts

Live Event Date: March 19, 2015 @ 1:00 p.m. ET / 10:00 a.m. PT
The 2015 Enterprise Mobile Application Survey asked 250 mobility professionals what their biggest mobile challenges are, how many employees they are equipping with mobile apps, and their methods for driving value with mobility.
Join Dan Woods, Editor and CTO of CITO Research, and Alan Murray, SVP of Products at Apperian, as they break down the results of this survey and discuss how enterprises are using mobile application management and private …

Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …