Link to home
Start Free TrialLog in
Avatar of Lenochka
Lenochka

asked on

Diamond-like multiple inheritance

I have the following structure:
       CObject
        |    |
        ...  ...
      CFoo1  CFoo2
(both derived from CObject somehow)
        |     |
         CFoo3
(multiple inheritance from CFoo1 and CFoo2)
Is there possible to make it work, so that the result of IsKindOf(CFoo1) and IsKindOf(CFoo2) for CFoo3 would be true?
(I saw technoteNo 16, but I beleive there is a way around).
Thanks.


Avatar of tomkeane
tomkeane

CObject always wants to be the left-most base class (i.e. the first class listed in the class' definitioin)
In the example below, CFoo1 is the leftmost base class, and is OK, CFoo2 is not and will have problems.

class CFoo3 : public CFoo1, public CFoo2

If you want to use MI with MFC, don't derive everything from CObject.

If all you want is the  IsKindOf() functionality, you can get this with standard C++ RTTI, using dynamic_cast<>.

ASKER CERTIFIED SOLUTION
Avatar of RONSLOW
RONSLOW

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi!
overriding non virtual method by virtual one is not good idea! (see any C++ book)
And other thinks:
diamond-like architecture can be achived by needed class aggregation and providing pointers to that members.for example:
      CObject
        |    |
        ...  ...
      CFoo1  CFoo2

class CFoo3:public CObject
{
private:
CFoo1 m_foo1;
CFoo2 m_foo2;
public:
operator CFoo1* (){return &m_foo1;}
operator CFoo2* (){return &m_foo2;}
}
migel .. not recommended if you are wrigint both the base and derived clases ..  but when you don't have control of the base class, that's all you can do.  Books are fine in theory .. but when it comes to 'badly' designed libraries .. you have to bend the 'rules' a little.

NOTE: your solution means a LOT of change to the program, or creating wrapper functions for each member of CFoo1 and CFoo2 ... not good.

eg. if CFoo1 has a member void f(), then you'd need to change calls from
  CFoo3 x; x.f();
to
  CFoo3 x; ((CFoo1*)x)->f();
of have to add to CFoo3 class
  void CFoo3:f() { m_foo1.f(); }
etc for all the memeber functions f

YUCK on both accounts.

And is you're really fussy, one can take my solution and change the name of the virtual function from 'IsKindOf' to 'IsKindOfEx' (say) and all is fine again re your objection to the function name.

hmm
RONSLOW>> I will not discuss with you. Bad programming technique can be total headache for further development. (so reading books is not only theory practice :-)
Diamond arch. is usefull when one using smart pointers and related things.
Yes .. I agree fully.  Bad techiques are a headache .. I criticse them myself .. especially when WHY they are bad (or even that there are caveats) is not explained.

Unfortunately, it is the bad techniques in MFC that are causing the problems here .. and sometimes it takes two wrongs to make a right.

MFC was not (in many ways) well designed in term of being extensible etc.  There are many things that one would long to have as virtual or public or whatever.  But they aren't and so one has to write one's code around what (in hindsight) is 'bad' design - and sometimes that means dong things we'd rather not and that are 'bad'.

Yes .. redefining a function as virtual in a derived class is (in general) bad design (from a pure point of view).

The reason it is bad is that is one calls the function from a pointer /reference to the base class, one will always get the base class function.  But it is not a problem if we never use pointers / references to the base class.  I mentioned that in my answer.

The advantage of the method I proposed is that it means minimal code changes and familiar looking code (still write with IsKindOf etc).

I'll grant you that if I named my virtual function IsKindOfEx that would better satisfy good technique guidelines.  One should also then define a private CObjectEx::IsKindOf so we cannot call it be accident.  Then code that previously called IsKindOf would need to change to call IsKindOfEx (hat is the price we pay with the name change).  Of course, there is STILL the danger of using CObject* pointers with IsKindOf .. so we'd still need to ensure we aren't using them.

A solution such as you proposed, while 'good' involves 'ugly' code and/or duplication of member function namesm and changes elsewhere in the code. etc.  

Not a nice design either.

I'm sure neither of us would WANT to write code this way if it weren't for the limitations / peculiarities / history of the MFC framework.

Of course, in an ideal world, we would simply use RTTI to do this, and multiple inheritance would work just fine.
:-)
Excuse me for my bad English. I haven`t enough lang. baggage to write so full explanation. I agree MFC is not OO fully compatible library :-(. but adding your own hacks to its clients is not good idea too.
of course adding nev virtual method IsKindOfEx.... will grant you correct results (and good technique also) but entire framework will not use it - only user classes know about new method.
That's ok. migel.  Your English is certainy good enough .. better than some 'native' english speakers I know :-)

If one doesn't add some sort of 'hack' then the answer to the original question is 'you cannot do it'.  Neither your nor my solution will work with only CObject* pointers and IsKindOf etc.

The only way is to use some other methods for determining RTTI.

Of course, what REALLY should be done is that the application is (re)written in an OOD way so that there is no NEED to know the type of an object, because everything happens correctly through virtal functions etc.

Bascially, its an ugly situation with an ugly solution.

Well .. at least we've had a nice chat.  Lenochka hasn't said anything yet .. maybe his/her problem has gone away?


I agree IsKindOf bad replacement of the std RTTI mechanism, but it is historical rudiment.
Lenochka>> Where are you? Is our discussion helps you?
MFC CRuntimeClass, IsKindOf etc predates RTTI support.

Why they couldn't have made CObject::IsKindOf a virtual I don't know.  CObject already has several virtual functions, adding another wouldn't hurt.  Then our job would be so much easier (just override IsKindOf for the CFoo3 class).

Maybe if we petition MS enough they might make it so?  (NOT)
Avatar of Lenochka

ASKER

Guys,
The discussion is very helpful, but the situation I'm in is more complicated that I initially presented.
I have 3rd party library (Go++, if you have heard about it), so I could not change the code inside this library.
MY CFoo2 is my own class derived from some class from Go++ (which are all derived from CObject), CFoo3 is the class from Go++. IsKindOf are inside Go++ source code. And that means that all your suggestions at this moment (and I tried them all - believe me) didn't bring the solution.
Hope this information will help.
Thanx.
I htnk I must be missing something here.

CFoo2 is your class .. but you are saying CFoo3 (the multiply inherited one) is in Go++.  How could a class in Go++ be derived from one of your classes?  Is it a template class?

Or was that a typo and you meant thatCFoo1 is a Go++ class and CFoo2 is yours?
RONSLOW>> Yes, it was mistypo - CFoo1 is mine and CFoo2 is Go's and I'm trying to create CFoo3.
Hi!
IsKindOf uses virtual method: CRuntimeClass* CObject::      GetRuntimeClass() const;
so you can override it and return GO++ object (but in this case your own classes will know about CFoo3 nothing :-( )
GetRuntimeClass is indeed virtual and is declared by the DECLARE_DYNAMIC macro and written by the IMPLEMENT_DYNAMIC macro.

You can, of course, bypass this and override it yourself, but that won't help here as we want CFoo3 to be simultaneously of two different types - or more to the point, to have two different base-classes.

However, all the other functions associated with MFC runtime type checking are all non-virtual, so you cannot override them .. if only they were virtual one could override CObject::IsKindOf so that it returned true for both CFoo2 and CFoo3

The other place one might have been able to do something is in the CRuntimeClass::IsDerivedFrom.  If it were virtual one could similarly return true for both CFoo2 and CFoo3, but again MS in their wisdom made all its functions non-virtual too.

brrr. so deeper catacombs :-)
Lenochka >> may be hierarchy changing will be preffered way?