Re: Multiple Inheritance Perplexities..
On Oct 6, 12:04 pm, neelsm...@rediffmail.com wrote:
Hi,
Here is the code I put together to understand multiple/diamond-shaped
inheritance. In last half an hour I got more questions than answers
about its working.. Please take a look. Most probably I have missed
something, but in case I haven't, it's a scary thing.. :-)
After more than 15 years of MI and virtual inheritance, it still
amazes me that many compilers still get this wrong (MSVC is not alone
in screwing up VI/MI somehow) as noted in the excellent post in this
topic. It is also amazing that the canonical introduction to VI/MI is
the "dreaded diamond" which is probably the worst example. That is why
it is scary.
But kudos to you for try to tackle this most interesting part of the
language, when many programmers are trying to do wild things with
template metaprogramming.
The best use of MI/VI is in policy based programming of
implementations of abstract base classes. This is a better example of
MI/VI and how a design might use it.
Your library has a client interface
struct Interface1{
virtual void foo1()=0;
virtual ~Interface1(){}
};
struct Interface2:virtual Interface1{
virtual void foo2()=0;
virtual ~Interface2(){}
};
struct Interface3{
virtual void foo3()=0;
virtual void foo3a()=0;
virtual ~Interface3(){}
};
std::auto_ptr<Interface1> Interface1Factory ( int);
std::auto_ptr<Interface2> Interface2Factory ( int);
std::auto_ptr<Interface3> Interface3Factory ( int);
You as the implementor have several behaviors for each interface you
would like
to "glue" together based on client needs
struct I1Stock1:virtual Interface1{
void foo1(){printf("I1stock1");}
protected: I1Stock1(){}
};
struct I1Stock2:virtual Interface1{
void foo1(){printf("I1stock2");}
protected:
I1Stock2(){}
};
struct I2Stock1:virtual Interface2{
void foo2(){printf("I2stock2");}
protected:
I2Stock1(){}
};
struct I2Stock2:virtual Interface2{
void foo2(){printf("I2stock2");}
protected:
I2Stock2(){}
};
struct I3Stock1:virtual Interface3{
void foo3(){printf("I3stock1");}
protected:
I3Stock1(){}
};
struct I3Stock2:virtual Interface3{
void foo3(){printf("I3stock2");}
protected:
I3Stock2(){}
};
struct I3Stock2a:virtual Interface3{
void foo3a(){printf("I3stock2a");}
protected:
I3Stock2a(){}
};
struct I3Stock2b:virtual Interface3{
void foo3a(){printf("I3stock2b");}
protected:
I3Stock2b(){}
};
And now a particular way of gluing the classes together
namespace{
struct Final1
:public virtual Interface3 //is-a relationship
,public virtual Interface1 //is-a relationship
,protected I3Stock2 //implemented in terms of relationship
,protected I1Stock1 //implemented in terms of relationship
,protected I3Stock2a //implemented in terms of relationship
{};
struct Final2
:public virtual Interface3
,public virtual Interface2
,protected I3Stock2
,protected I1Stock1
,protected I2Stock1
,protected I3Stock2b
{};
struct Final3
:public virtual Interface1
,protected I1Stock2
{
void foo1(){
printf("Special Case");
I1Stock2::foo1();
}
};
//more final classes
}//anon namespace
std::auto_ptr<Interface1> Interface1Factory(int choice){
switch(choice){
case 0: return std::auto_ptr<Interface1>(new Final1);
case 1: return std::auto_ptr<Interface1>(new Final2);
}
return std::auto_ptr<Interface1>(new Final3);
}
//the other factories
You can see now a clear separation of interface and implementation.
Client code should only use the interfaces
void foo(int arg){
tr1::shared_ptr<Interface1> i1(Interface1Factory(a));
i1->foo1();//depends on value of arg
tr1::shared_ptr<Interface3>
i3=tr1::dynamic_pointer_cast<Interface3>(i1);
if(i3)//depends on value of arg
i3->foo3();
//client code wants to break the rules
tr1::shared_ptr<I1Stock1>
peek=tr1::dynamic_pointer_cast<I1Stock1>(i1);;
assert(!peek);//most compilers get this right --
//cannot dynamic_cast because I1Stock1 is protected
//and the anon namespace make the "Final*" names only visible to
the file implementing
// the factory
//so client must use interfaces
}
This style programming is more of what was in mind for MI/VI, and as
you can see we are unconcerned about diamonds or whatever shapes are
in the class hierarchy graph.
It is true that some people (notably IOStreams) uses MI/VI to marry
together classes that could otherwise stand alone. It is when you try
to join standalone classes that you get all this dread about diamonds.
Too bad this seems to be the ONLY example found in the vast majority
of the literature.
The ATL library uses the design I just descibed, except that it has
its own proprietary method of doing VI --only because COM does not
understand C++ virtual inheritance.
Hope this helps
Lance
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]