A lot of programmers scoff at multiple inheritance, and it is not something I would use very often, but there are times when it makes sense. Recently I did some refactoring to some dialog classes, which are based on MFC. Looking at what we had, it was a perfect case for using multiple inheritance. But then I found out after trying to implement it that apparently the folks who wrote MFC don’t believe in multiple inheritance either. They have this to say in the linked article:
We have found that MI is not required to write a class library, nor is it required for writing serious applications.
Are you kidding me?! That’s a rather condescending remark to say the least, especially from the folks who brought us COM! I would suggest to Microsoft that MFC is not required for writing serious applications either.
The Details…
What I had to deal with were some dialogs that are assembled on the fly. We have control classes that do special things, so naturally there is a class for each one: label, checkbox, edit, button, etc. The previous implementation used a wrappered approach: there was a class for each control and private to that class was an instance of an MFC control. Then each class defined its own methods for all of the usual control functions, like positioning, font, color, etc. with each method operating on its own private control. A lot of redundant code, and compounding it was a lot of application-specific code repeated in each class as well.
So my main goal for this refactor was to eliminate as much redundant code as possible.
First I needed a base class for my controls, to handle the application functions common to all of them. This eliminated a lot of redundancy, but I still had the same SetText, SetColor, etc. defined in each class. Here is where multiple inheritance would be key. I wanted to make my base control class a descendant of CWnd (base class to MFC controls), so that I could handle all of the common control operations in one class. Then I would make each of my control classes descendants of the MFC controls they implement. Only the functions specific to each control, like handling a checkbox would need to go in the descendant classes. 90% of the work can be done in one base class.
The structure is like this:
class CMyControl : virtual public CWnd {…}
class CMyEdit : public CEdit, public CMyControl {…}
With this structure, I can handle all of the basic control operations, like setting text, changing colors, moving, etc. in CMyControl. Each derived control class can deal only with what it needs to.
You might be wondering, aren’t there two copies of CWnd in the v-table? One for CMyControl’s base class, and one for CMyEdit’s instance of CEdit? Normally there would be, and that would be a problem, but that’s why C++ gives you the option to declare a virtual base class. This tells the compiler to keep only one instance of CWnd in the class, so any operations on CMyControl’s CWnd will actually operate on CEdit’s CWnd.
It is a beautiful design, and one that eliminates all redundant code. Except for one small problem: IT WON’T COMPILE!!
It won’t compile because of how MFC is implemented, and given the remark I quoted above, I think some folks at Microsoft need to go back to OOP school.
The workaround I ended up using, was to remove the virtual base class, and instead use a GetCWnd method so that each descendant class can return a pointer to it’s underlying CWnd. I made GetCWnd a pure virtual method in the base class so that each descendant is required to implement it.
So now, instead of a nice clean call like this:
mycontrol.SetWindowText("This is great!");
I have to write:
mycontrol.GetCWnd()->SetWindowText("This really blows!");
Sure, I could go and write a pass-through method for each and every CWnd method, but who wants to do that?
Even worse, MFC message handlers have to be repeated in each descendant class! Ugh. I’ve got handlers for things like CtlColor and OnSetCursor. It’s the exact same code for each class, but now I have to copy and paste it into each of my control classes. Note: if you find yourself cutting and pasting code into multiple classes, then something is wrong!
All in all, the classes are much better than they were, but they could have been perfect.
oh no! you are getting rid of my lovely checkbox and label classes!!
lol. Don’t worry, I grafted the good parts.
“Are you kidding me?! That’s a rather condescending remark to say the least, especially from the folks who brought us COM!”
COM doesn’t use multiple inheritance… it uses multiple interface implementations. COM is simply a set of interfaces, the concrete classes implement those. Just because a class implements multiple interfaces doesn’t mean it has multiple vtables. You don’t hit that until you get into the really crazy stuff like aggregates, inner and outer objects and the like. And they’re right, multiple inheritance is almost never needed in the real world, which is why you see a lot of languages going away from it.
Why aren’t you using interfaces instead of trying to jam multiple inheritance into the picture? It sounds like you just want to have all these classes implement a base subset of functionality, while still being concrete instances of their proper classes. So, for instance, all controls can set their position and size, and you want to reference that in a generic fashion. So make an interface which exposes that functionality generically, and be done with it. It makes no sense for a control to be both a CWnd and a CSomeOtherControl…
I never said COM was based on MI, just that it’s a horrible construct. But so is MFC.
An interface is exactly what GetCWnd is. My problem is that writing somecontrol.GetCWnd()->MoveWindow(here) is far more ugly than somecontrol.MoveWindow(here).
COM is a whole other can of worms. Now you’ve got to CoCreateInstance and QueryInterface your way through nested if’s and hope your programmers are checking their returns, and freeing their bstrs and such, and fixing their cut-and-pasted sample code because 98% of it is wrong!
And tell me again how I can implement a CtlColor handler ONCE, for all 5 of my control classes?
Yeah, I just re-read your post — I think my cold is getting the better of me.
I think the nomenclature was what tripped me up since you described interfaces in the context of multiple inheritance.
You’re right that what you were describing was an interface, but I think you got the syntax slightly wrong. Simply declaring the class as virtual doesn’t make it pure virtual, which is what’s needed to make an interface in C++.
What I *think* (and I could be off-base!) you’re looking for is something more like this:
class MyControl {
virtual void MoveWindow( ... ) = 0L;
virtual void Blah( ... ) = 0L;
};
class MyAwesomeEdit : public CEdit, public MyControl {
...
};
This would allow you to pass things around as MyControl (generically), but still have concrete MFC objects under the hood. However, I may not be understanding exactly what you’re after either (damned cold!).
Oh, and another interesting thing to note: unless CEdit also declares that it inherits CWnd virtually, then you will still end up with two vtables in your CEdit subclass — one virtual, the other not. Goes to show just how evil multiple inheritance really is.
Well, no that’s not what I was after. What I want is to avoid rewriting methods that already exist. All of the stuff that already exists in CWnd, I just want to be able to call it, directly.
My classes are simple:
class CMyControl : virtual public CWnd {…}
class CMyEdit : public CEdit, public CMyControl {…}
The class that uses CMyControl (a dialog class, go figure) doesn’t need to know if it’s an edit control, or a checkbox control, or what. It just needs to know that it’s a “contorl” and it can go ahead and position it, or set its font, or change its color, etc. The descendant classes to CMyControl - like CMyEdit - will implement those functions. I don’t need to worry about it, because the underlying MFC controls already do those things. But, if I’m the dialog class and all I have is a pointer to CMyControl, then I need to get a hold of a pointer to the CWnd object to do those things.
Also, I don’t think you’re last statement is correct. Delcaring a pure virtual base class (I believe) is just like declaring a virtual method in a base class. Once you’ve done it, the descendants are automatically virtual whether you use the keyword or not.
Last thing first: check out this link to MSDN, specifically the Virtual and Nonvirtual Inheritance section.
http://msdn2.microsoft.com/en-us/library/wcz57btd(VS.80).aspx
I finally think I understand what you’re after now though.
You have a bunch of CWnd controls (like CEdit, CButton, etc), but you also want them to inherit from your common set of “Control” functionality. But your Control functionality wants to be a CWnd as well so it can manipulate the control as a base window object. However, you can’t make Control inherit from CWnd because of multiple vtables. Basically correct?
Well, yeah, the virtual base class is what would make this work, but the problem is you get compile errors because of how MFC is implemented (something to do with the message mapping). That was one of the articles I linked (the one that says MI is not required for serious applications).
The reason I want to do it this way, is because 95% of the code for my control class is the same. It’s mainly one method which reads the dialog control info from a file - the text, color, font, position, etc. So all of that code fits nicely in my base class, and from the outside I just need to call a generic LoadFromFile method, and the class does everything. The descendant classes can override this method to add any special items, like a flag for a checkbox for instance.
There are some other basic methods, like adding a bitmap, which one control might handle differently than another.
My solution to use GetCWnd() method to get the class ptr I need is not so bad. The thing I’m more unhappy about is not being able to implement message handlers such as CtlColor and OnSetCursor in the base class. If I’ve got 5 control classes, now I have 5 copies of the same handlers. I loathe redundant code, it keeps me up at night.
Weird…well, I’ve always avoided MFC whenever possible, so this is entirely likely to be out of my scope. I hate MFC and don’t envy you!
In any event, nice blog! Steve pointed it out to me, in case you’re wondering where I came from.
I know, not my choice to use MFC either. Yeah I saw your name on Steve’s blogroll. Thanks for the conversation!