Virtual inheritance

M

Massimo

Hi to all, I'm facing a problem in a particularly complex inheritance
hierarchy, and I'd like to know what the standard says about it and if my
compiler is correct in what it does.

I have two consecutive layers of diamond-shaped inheritance; the first layer
is declared as using virtual inheritance, while the second one is declared
as *not* using it.

This is what I'd like to have:

Base Base
/ \ / \
D1Base D2Base D1Base D2Base
\ / \ /
Middle Middle
|| ||
D1Middle D2Middle
\ /
\ /
\ /
Last

This is what the compiler actually does:

Base
/ \
D1Base D2Base
\ /
Middle
/ \
D1Middle D2Middle
\ /
Last

If I remove all virtual inheritance, everything goes just fine; if I use it
in the first layer, there seems to be no way of disabling it afterwards.

Does the standard saying anything about this, or is it
implementation-dependant? My compiler is Visual Studio 2005.

Code sample attached.


Thanks


Massimo
 
J

jg

Hi to all, I'm facing a problem in a particularly complex inheritance
hierarchy, and I'd like to know what the standard says about it and if my
compiler is correct in what it does.

I have two consecutive layers of diamond-shaped inheritance; the first layer
is declared as using virtual inheritance, while the second one is declared
as *not* using it.

This is what I'd like to have:

Base Base
/ \ / \
D1Base D2Base D1Base D2Base
\ / \ /
Middle Middle
|| ||
D1Middle D2Middle
\ /
\ /
\ /
Last

This is what the compiler actually does:

Base
/ \
D1Base D2Base
\ /
Middle
/ \
D1Middle D2Middle
\ /
Last

I think the object of Last will have a single subobject of Base,
not two. The other subobjects are not shared (i.e. there are
two subobjects each for D1Base, D2Base, Middle).

Base
/ / \ \
/ / \ \
/ / \ \
D1Base D2Base D1Base D2Base
\ / \ /
Middle Middle
| |/
D1Middle D2Middle
\ /
\ /
\ /
Last

JG
 
M

Massimo

I think the object of Last will have a single subobject of Base,
not two. The other subobjects are not shared (i.e. there are
two subobjects each for D1Base, D2Base, Middle).

Base
/ / \ \
/ / \ \
/ / \ \
D1Base D2Base D1Base D2Base
\ / \ /
Middle Middle
| |/
D1Middle D2Middle
\ /
\ /
\ /
Last

I really don't know, but don't think so, since each and every one of these
classes is (somewhat) a subclass of Base, and so inherits (or seems to
inherit) the virtual inheritance setting.

I'm actually quite sure there's only one Middle object, because the compiler
doesn't complain about method names resolution issues... while it *does*, if
inheritance is made non-virtual.

Anyway, any clue on how to obtain the desidered behaviour (if this is at all
possible)?


Massimo
 
G

Greg Herlihy

I really don't know, but don't think so, since each and every one of these
classes is (somewhat) a subclass of Base, and so inherits (or seems to
inherit) the virtual inheritance setting.

I'm actually quite sure there's only one Middle object, because the compiler
doesn't complain about method names resolution issues... while it *does*, if
inheritance is made non-virtual.

Anyway, any clue on how to obtain the desidered behaviour (if this is at all
possible)?

The problem is that every class that declares a virtual base class
shares that base class object with every other class that has declared
the same virtual base class. Therefore it is not possible to declare
D1Base and D2Base in such a way that the two pairs of D1Base and
D2Base objects share the same Base object as one another - without
also having them share the same Base object as every other pair of
D1Base and D2Base objects in the hierarchy. In other words, it is
possible to reduce the top of the current inheritance graph to a
single Base object - or three or four Base class objects - but not two
- as the graph is currently structured.

One way to remedy this problem - and to have the two Base objects at
the top of the hierarchy as desired - would be to insert a pair of
"buffer" classes, Base1 and Base2, between the Base and the D1Base and
D2Base objects. D1Base and D2Base would then each inherit from both
Base1 and Base2 as virtual base classes. The rest of the current
inheritance hierarchy would remain the same.

To illustrate. With this change the top level of the inheritance graph
would like this:

Base Base
| |
| |
Base1 Base2
| | | |
| \ / |
| \ / |
| \/ |
| /\ |
| / \ |
| / \ |
| | | |
D1Base D2Base

And the revised class declarations at the top of hierarchy would look
like:

class Base
{
int n;
};

class Base1 : public Base
{
};

class Base2 : public Base
{
};

class D1Base : public virtual Base1, public virtual Base2
{
};

class D2Base : public virtual Base1, public virtual Base2
{
};

The rest of the inheritance hierarchy would remain the same.

Greg
 
J

Juha Nieminen

jg said:
Base
/ / \ \
/ / \ \
/ / \ \
D1Base D2Base D1Base D2Base
\ / \ /
Middle Middle
| |/
D1Middle D2Middle
\ /
\ /
\ /
Last

If you are going to draw ascii art, it would be a good
idea to use fixed-width font, wouldn't you think? That looks
like gibberish when looked with a fixed-width font.
Using variable-width font to draw ascii art is insane because
it assumes everyone else is using the exact same font as you are.
 
J

James Kanze

Hi to all, I'm facing a problem in a particularly complex inheritance
hierarchy, and I'd like to know what the standard says about it and if my
compiler is correct in what it does.
I have two consecutive layers of diamond-shaped inheritance; the first layer
is declared as using virtual inheritance, while the second one is declared
as *not* using it.
This is what I'd like to have:
Base Base
/ \ / \
D1Base D2Base D1Base D2Base
\ / \ /
Middle Middle
|| ||
D1Middle D2Middle
\ /
\ /
\ /
Last

Which is impossible in C++.
This is what the compiler actually does:
Base
/ \
D1Base D2Base
\ /
Middle
/ \
D1Middle D2Middle
\ /
Last

That's not what it does given the code you posted. What you're
actually getting is more like.

------Base------
/ / \ \
/ / \ \
D1Base D2Base D1Base D2Base
\ / \ /
Middle Middle
|| ||
D1Middle D2Middle
\ /
\ /
\ /
Last
If I remove all virtual inheritance, everything goes just
fine; if I use it in the first layer, there seems to be no way
of disabling it afterwards.

Virtual inheritance only affects the classes which derive using
it. On the other hand, all instances of all classes which
derive from Base will share a common instance. You can't group,
with two or three different groups of classes sharing a common
instance.

What you might try is making Base, the two DnBase and Middle
templates (without changing anything else), and instantiating
them on eithre D1Middle or D2Middle. By doing this, the 2
Middle, each of the DnBase, and the two Base are formally
different types. This could result in significant code
duplication, of course.
Does the standard saying anything about this, or is it
implementation-dependant? My compiler is Visual Studio 2005.

The standard specifies very precisely what abstract graph you
should end up with. All of the compilers I have access to
(VC++, Sun CC and g++) are conform in this respect, and
generate to diagram I've given with your code.
 
J

jg

If you are going to draw ascii art, it would be a good
idea to use fixed-width font, wouldn't you think? That looks
like gibberish when looked with a fixed-width font.
Using variable-width font to draw ascii art is insane because
it assumes everyone else is using the exact same font as you are.

Aha, didn't know this would have made you unhappy. Knowing
that I did not choose to do so could probably make you happy
again. :) Yeah, your point taken !
 
M

Massimo

To illustrate. With this change the top level of the inheritance graph
would like this:

Base Base
| |
| |
Base1 Base2
| | | |
| \ / |
| \ / |
| \/ |
| /\ |
| / \ |
| / \ |
| | | |
D1Base D2Base

And the revised class declarations at the top of hierarchy would look
like:

class Base
{
int n;
};

class Base1 : public Base
{
};

class Base2 : public Base
{
};

class D1Base : public virtual Base1, public virtual Base2
{
};

class D2Base : public virtual Base1, public virtual Base2
{
};

This way, there would be two different "n" in Base1 and Base2, and so D1Base
would have *two* n's, as would D2Base. This is not what i'm looking for... I
want D1Base to have *only one* n (here the virtual inheritance need), and
D2Base to also have *only one* n; but I need those two n's to be two
different ones when deriving from both D1Base and D2Base.

I think the only way to obtain this is to *not* derive subsequent classes
from both, but to put a couple of Middle objects inside Last as members;
this is not exactly what I needed, but it will at least work...

But I'm still curious on why the "virtual" setting for the inheritance is...
inherited, even if I've specified "virtual" only when deriving from Base,
and not when deriving subsequent classes. It just seems to have no effect on
subsequent derivations, making it actually redundant.


Massimo
 
M

Massimo

"James Kanze" <[email protected]> ha scritto nel messaggio
Virtual inheritance only affects the classes which derive using
it.

Why is it in effect when I'm sub-deriving in a non-virtual manner, then?
On the other hand, all instances of all classes which
derive from Base will share a common instance.
You can't group, with two or three different groups of classes
sharing a common instance.

Yes, I've come to understand this.
Is the "virtual" keyword on sub-derivations redundant, then?
What you might try is making Base, the two DnBase and Middle
templates (without changing anything else), and instantiating
them on eithre D1Middle or D2Middle. By doing this, the 2
Middle, each of the DnBase, and the two Base are formally
different types.

This is a good suggestion; can you elaborate a bit? Specifically, what do
you mean by "instantiating"? Making DxMiddle a derived class of a
specialized Middle<T>? I think this wouldn't work; actually, before
semplifying it for posting, the real code involved Base, D1Base, D2Base and
Middle being template classes, and D1Middle being derived from
Middle<double>. D2Middle was derived from Middle<double> too, and the
purpose of D1Middle and D2Middle being different classes was exactly to be
able to derive (non-virtually!) Last from both of them, so having two
different Middle<double> inside a Last. But I'm stuck with undesidered
virtual inheritance, so members of D1Middle and D2Middle end un accessing
the same data.


Massimo
 
J

James Kanze

"James Kanze" <[email protected]> ha scritto nel messaggio
Why is it in effect when I'm sub-deriving in a non-virtual manner, then?

I don't get what you're trying to say. If you write something
like:

class A : public B {} ;

B is NOT a virtual base. Period, and regardless of anything
else in the program. There will be one instance of B for every
instance of A, always.
Yes, I've come to understand this.
Is the "virtual" keyword on sub-derivations redundant, then?

No. Either you inherit virtually, and the keyword is necessary,
or you don't inherit virtually, and the keyword cannot be used.
This is a good suggestion; can you elaborate a bit? Specifically, what do
you mean by "instantiating"?

Do you know what a template is? Do you know what it means to
instantiate it.
Making DxMiddle a derived class of a
specialized Middle<T>? I think this wouldn't work; actually, before
semplifying it for posting, the real code involved Base, D1Base, D2Base and
Middle being template classes, and D1Middle being derived from
Middle<double>. D2Middle was derived from Middle<double> too, and the
purpose of D1Middle and D2Middle being different classes was exactly to be
able to derive (non-virtually!) Last from both of them, so having two
different Middle<double> inside a Last. But I'm stuck with undesidered
virtual inheritance, so members of D1Middle and D2Middle end un accessing
the same data.

The trick is to instantiate one side on D1Middle, and the other
on D2Middle.

Note that there's nothing in the rules which says that a
template has to use the instantiation type. Something like:

template< typename T >
class Base
{
// as before...
} ;

is perfectly legal. The class will behave exactly the same no
matter what type it is instantiated on, *but* it will have a
different type. Once you do this, you can then define the other
classes as:

template< typename T >
class D1Base : public virtual Base< T >
{
// as before...
} ;

template< typename T >
class D2Base : public virtual Base< T >
{
// as before...
} ;

template< typname T >
class Middle : public D1Base< T >, public D2Base< T >
{
// as before...
} ;

class D1Middle : public Middle< D1Middle >
{
// as before...
} ;

class D2Middle : public Middle< D2Middle >
{
// as before...
} ;

class Last : public D1Middle, public D2Middle
{
// as before...
} ;

This will result in the hierarchy you said you wanted, with the
exception that all of the classes on the left side will in fact
be instantiations on D1Middle, and all on the right side will in
fact be instantiations on D2Middle. (Note that this may affect
your external interface. There's no longer any such class a
Base, for example. And it typically means hoisting a lot of
code up into the headers---not necessarily something you want to
do either.)
 
M

Massimo

I don't get what you're trying to say. If you write something
like:

class A : public B {} ;

B is NOT a virtual base. Period, and regardless of anything
else in the program. There will be one instance of B for every
instance of A, always.

That's exaclty what I would expect.
But then, if B has a virtual base C, and I do

class A1 : public B {};
class A2 : public B {};
class D: public A1,public A2 {};

As you said, B, is not a virtual base, and I should have two instances of B:
one in A1 and the other in A2. Each of these *should* contain its own base
classes, up to and including C. Why is it, then, that when I re-derive from
both of them the instance of C becomes unique? This is what is confusing me.
Note that there's nothing in the rules which says that a
template has to use the instantiation type. Something like:

template< typename T >
class Base
{
// as before...
} ;

is perfectly legal. The class will behave exactly the same no
matter what type it is instantiated on, *but* it will have a
different type.

That's great, I'll give it a try. Thanks.


Massimo
 
J

James Kanze

"James Kanze" <[email protected]> ha scritto nel messaggionews:[email protected]...
That's exaclty what I would expect.
But then, if B has a virtual base C, and I do
class A1 : public B {};
class A2 : public B {};
class D: public A1,public A2 {};
As you said, B, is not a virtual base, and I should have two instances of B:
one in A1 and the other in A2.

You do.
Each of these *should* contain its own base
classes, up to and including C.

No. When you defined B, you said that you want all instances of
B in a single hierarchy to share the same C. That's what the
virtual means in inheritance. More generally, it means that
*all* classes which inherit virtually from C share the same
instance, regardless of where they might be in the hierarchy.
Why is it, then, that when I re-derive from
both of them the instance of C becomes unique?

Because you told the compiler that that's what you wanted.

One way of looking at it is that the virtual base class is in
fact provided by the most derived class. It is the most
derived class which will call its constructor, for example, and
it is the most derived class which will determine where it is
physically located. Even if the author of the most derived
class doesn't even know it is there. So when you inherit
virtually, you are more or less saying I want a single instance
to be provided by the most derived class.
This is what is confusing me.

Just curious, but what would you expect if in addition, D
derived virtually from C, i.e.:

class B : public virtual C {} ;
class A1 : public B {} ;
class A2 : public B {} ;
class D : public A1, public A2, public virtual C {} ;

Note that you can have addional instances of C:

class B : public virtual C {} ;
class A1 : public B, public C {} ;
class A2 : public B {} ;
class D : public A1, public A2, public virtual C {} ;

This hierarchy contains two instances of C, one for all of the
virtual inheritance, and one for A1.

If you want a somewhat clearer view, try starting with:

class C
{
public:
C( std::string const& i )
{
std::cout << "C initialized by " << i << std::endl ;
}
} ;

and experimenting with different hierarchies, each time adding
an initializer for C with the class name, e.g.:

B::B() : C( "B" ) {}
A1::A1() : C( "A1" ) {}
// ...

You'll find that:

-- there's no problem specifying the initializer, as long as
there is a virtual inheritance somewhere below the class in
the hierarchy -- you don't have to specify it in the class
doing the initialization --, and

-- the constructor for C will always (without exception) be
called from the most derived class.
 
M

Massimo

One way of looking at it is that the virtual base class is in
fact provided by the most derived class. It is the most
derived class which will call its constructor, for example, and
it is the most derived class which will determine where it is
physically located. Even if the author of the most derived
class doesn't even know it is there. So when you inherit
virtually, you are more or less saying I want a single instance
to be provided by the most derived class.

That's a lot clearer, thanks.


Massimo
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,189
Latest member
CryptoTaxSoftware

Latest Threads

Top