Topic: virtual inheritance problems
Author: Alexandre Oliva <oliva@dcc.unicamp.br>
Date: 1996/09/30 Raw View
>>>>> In article <1y919ubsla.fsf@dshp01.trs.ntc.nokia.com>,
>>>>> girod@dshp01.trs.ntc.nokia.com (Marc Girod) writes:
>>>>> "AO" == Alexandre Oliva <oliva@dcc.unicamp.br> writes:
AO> The code is bad, because there are two instances of B and C for each
AO> F, and a single A, so there's not a single overrider, and the
AO> compilation should fail.
> You override on a class basis, not on an instance basis. There would
> be a problem if C would override foo as well, but the number of B
> sub-objects plays no role whatsoever.
I've been re-reading the standard, and I noticed (as far as I can
tell) that it is not clear what should happen in this case. I'll
repost part of the code for context:
class A { public: virtual void foo() = 0; };
class B : public virtual A { public: void foo(); };
// class C elided
class D : public /* not virtual! */ /* final */ B {};
class E : public /* not virtual! */ /* final */ B {};
class F : public D, public E {
/* must foo() be overridden in F? */
/* void foo(); */
};
I've been arguing that the declaration of class F is invalid, since
there is no unique final overrider for method foo(). My reasoning is
that the compiler is unable to decide which B sub-object the
invocation should be made upon, F::D::B or F::E::B, so the code should
be invalid, as is "struct Error" in [class.virtual], May'96 DWP.
> They both share one common virtual table, and foo is provided only
> once.
F::E::B and F::D::B are different sub-objects of F, so they are placed
at different offsets, and any virtual method declared in B and
implemented, in order to be invoked, should have the pointer to this
fixed as necessary. Hence, they can't share a single virtual table!
The whole point here is that the virtual table for F::A cannot be
built, since the compiler cannot decide which is the correct
this fixup for invoking method B::foo().
Regards,
--
Alexandre Oliva
mailto:oliva@dcc.unicamp.br
Universidade Estadual de Campinas, SP, Brasil
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: girod@dshp01.trs.ntc.nokia.com (Marc Girod)
Date: 1996/09/25 Raw View
>>>>> "AO" == Alexandre Oliva <oliva@dcc.unicamp.br> writes:
AO> The most important problem to be solved with this proposal is actually
AO> not the initialization problem, but the problem that arises when two
AO> (supposed independent) branches or a hierarchy tree are merged into a
AO> single class:
[ code elided ]
AO> The problem here is that E::C::bar() supposes that foo() will invoke
AO> its brother E::B::foo(), but it will not --- this code does not
AO> compile because there's not one final overrider for foo(). It would
AO> be necessary to define F::foo() and F::bar() for this code to compile,
AO> but then neither would be able to distinguish foo() invocations from
AO> the D or from the E branch.
No!? This code is correct, and does compile (barring the '3D's - what
are they an artifact of?).
Because of virtual inheritance, there is only one foo 'slot' in A's
specializations and it is filled in with B::foo (provided one gives a
definition, of course).
Now of course, you have several B and C sub-objects in every F, which
is a headache.
Best Regards!
--
Marc Girod Phone: +358-0-511 27703
Nokia Telecommunications P.O. Box 12 Fax: +358-0-511 27432
Kilo RD 4 FIN-02611 Espoo marc.girod@ntc.nokia.com
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: Alexandre Oliva <oliva@dcc.unicamp.br>
Date: 1996/09/27 Raw View
>>>>> In article <1yranrnosc.fsf@dshp01.trs.ntc.nokia.com>,
>>>>> girod@dshp01.trs.ntc.nokia.com (Marc Girod) writes:
>>>>> "AO" == Alexandre Oliva <oliva@dcc.unicamp.br> writes:
AO> The most important problem to be solved with this proposal is actually
AO> not the initialization problem, but the problem that arises when two
AO> (supposed independent) branches or a hierarchy tree are merged into a
AO> single class:
> [ code elided ]
AO> The problem here is that E::C::bar() supposes that foo() will invoke
AO> its brother E::B::foo(), but it will not --- this code does not
AO> compile because there's not one final overrider for foo(). It would
AO> be necessary to define F::foo() and F::bar() for this code to compile,
AO> but then neither would be able to distinguish foo() invocations from
AO> the D or from the E branch.
> No!? This code is correct, and does compile (barring the '3D's - what
> are they an artifact of?).
(this is MIME quoted-printable format; unfortunately sometimes I
forget to disable MIME quoting :-(
The code is bad, because there are two instances of B and C for each
F, and a single A, so there's not a single overrider, and the
compilation should fail.
> Because of virtual inheritance, there is only one foo 'slot' in A's
> specializations and it is filled in with B::foo (provided one gives a
> definition, of course).
Which B subobject would that B be :-), F::E::B or F::D::B?
> Now of course, you have several B and C sub-objects in every F, which
> is a headache.
This would be a headache if you cared about D and E internals, but you
need not care about them, you just intend to extend them in F.
--
Alexandre Oliva
mailto:oliva@dcc.unicamp.br
Universidade Estadual de Campinas, SP, Brasil
[ comp.std.c++ is moderated. To submit articles: try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: girod@dshp01.trs.ntc.nokia.com (Marc Girod)
Date: 1996/09/28 Raw View
>>>>> "AO" == Alexandre Oliva <oliva@dcc.unicamp.br> writes:
AO> The code is bad, because there are two instances of B and C for each
AO> F, and a single A, so there's not a single overrider, and the
AO> compilation should fail.
You override on a class basis, not on an instance basis. There would
be a problem if C would override foo as well, but the number of B
sub-objects plays no role whatsoever.
>> Because of virtual inheritance, there is only one foo 'slot' in A's
>> specializations and it is filled in with B::foo (provided one gives a
>> definition, of course).
AO> Which B subobject would that B be :-), F::E::B or F::D::B?
They both share one common virtual table, and foo is provided only
once.
The code is OK.
Best Regards!
--
Marc Girod Phone: +358-0-511 27703
Nokia Telecommunications P.O. Box 12 Fax: +358-0-511 27432
Kilo RD 4 FIN-02611 Espoo marc.girod@ntc.nokia.com
[ comp.std.c++ is moderated. To submit articles: try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: Alexandre Oliva <oliva@dcc.unicamp.br>
Date: 1996/09/20 Raw View
>>>>> In article <m0v2mMF-000IDOC@venus.mcs.com>, maney@mcs.com (Martin J. =
Maney) writes:
> In an article in comp.std.c++ you wrote:
>> Another problem arises when two independent hierarchies, that both
>> inherit from virtual A, are merged in a single derived class: the
>> independence breaks. Although this is the basis for mix-in idioms, it
>> would be desirable to be able to control this behaviour.
> The simple answer is that virtual base classes should be pure
> abstract, in which case all the problems of initialization go away
> (because a really abstract class has no data members that require
> initialization).
The most important problem to be solved with this proposal is actually
not the initialization problem, but the problem that arises when two
(supposed independent) branches or a hierarchy tree are merged into a
single class:
class A {
public:
virtual void foo() =3D 0;
virtual void bar() =3D 0;
};
class B : public virtual A {
public:
void foo();
};
class C : public virtual A {
public:
void bar() { foo(); }
};
class D : public B, public C {};
class E : public B, public C {
public:
void baz() { bar(); }
};
class F : public D, public E {
public:
void dosomething() { baz(); }
};
The problem here is that E::C::bar() supposes that foo() will invoke
its brother E::B::foo(), but it will not --- this code does not
compile because there's not one final overrider for foo(). It would
be necessary to define F::foo() and F::bar() for this code to compile,
but then neither would be able to distinguish foo() invocations from
the D or from the E branch.
Notice how this breaks code when it would not be needed. It may seem
crazy to make code like that but it is not that hard to be caught by
such problems if you really think on behavior, and abstraction is one
of the most important concepts on object orientation.
If it were possible to say "Ok, whoever inherits from me should not
take class A as virtual base", all this would be fixed, and it would
not be that hard to implement it this way. Even if someone did not
declare
class D : public B, public C, public final A {};
and the same for class E, class F could have indirectly inherited from
D and E, keeping a different sub-instance of A for each one, by
declaring:
class D_ : public D, public final A {};
class E_ : public E, public final A {};
class F : public D, public E {};
But this is current IMPOSSIBLE to do in C++, and I remember of really
NEEDING such a feature a long time ago, and having to do a HUGE
workaround because of not having this feature.
> Consider Java's "interface" class - that's just what a true abstract
> base class is, all interface and nothing that will ever require
> initialization.
Java does not have multiple inheritance of implementation, so the
problem I describe does not arise at all. If MI were ever introduced
into Java, the same problem of multiple implementations for the same
method would have to be considered and dealt with.
>> 2) It would also be good to allow a class to delegate virtual base
>> classes initialization to the first base class that would initialize
>> it. =3D20
>> class A { public: A(int); A(char *); };
>> class B : public virtual A { B(int i) : A(i) {} };
>> class C : public virtual A { C(char *c) : A(c) {} };
>> class D : public B, public C { D() : !A, B(1), C("") {} };
>> In this case, B would initialize A. The only additional restriction
>> is that neither A nor its members may be referred to before subobject
>> B is initialized.
> What does this buy you? You still need to know, when writing class D,
> about the initialization requirements of A (unless A is truly abstract
> <grin>): all this adds is the need to know that one of the intermediate
> classes can perform the initialization. D is still controlling the
> initialization, it's only doing it at one or more steps removed. This is=
> not an improvement in my opinion, as it increases rather than decreases
> the number of classes that must be understood in order to make changes to=
> ANY of these classes.
As I wrote in my original post, this additional construct is only
necessary to prevent already-developed code from breaking with this
change of behavior. It could also be defined that virtual base
classes are initialized by the first class that provides an
initializer in a depth-first search in the inheritance graph (this
would be quite straightforward to implement), but, once again, this
change would break existing code.
So I suggested an alternate construct to provide implementors some
means to centralize the maintenance for initialization of base
classes. That is, if, at some point in time, it is decided that A
should now be initialized with some arguments, you have to change only
one point in the program, instead of having to change every subclass
that could have inherited from it.
> Again, if A requires no explicit initialization, this problem goes away.
Of course. I agree that pure-virtual base classes solves this
problem, and I don't really think this additional complexity for
delaying initialization of virtual base classes is necessary, but it
would be useful for those cases where you, for some reason, don't want
the virtual base class to be completely abstract (such as a Java
interface).
--
Alexandre Oliva
mailto:oliva@dcc.unicamp.br
Universidade Estadual de Campinas, S=E3o Paulo, Brasil
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: Alexandre Oliva <oliva@dcc.unicamp.br>
Date: 1996/09/20 Raw View
Reposting article removed by rogue canceller.
>>>>> In article <m0v2mMF-000IDOC@venus.mcs.com>, maney@mcs.com (Martin J. =
Maney) writes:
> In an article in comp.std.c++ you wrote:
>> Another problem arises when two independent hierarchies, that both
>> inherit from virtual A, are merged in a single derived class: the
>> independence breaks. Although this is the basis for mix-in idioms, it
>> would be desirable to be able to control this behaviour.
> The simple answer is that virtual base classes should be pure
> abstract, in which case all the problems of initialization go away
> (because a really abstract class has no data members that require
> initialization).
The most important problem to be solved with this proposal is actually
not the initialization problem, but the problem that arises when two
(supposed independent) branches or a hierarchy tree are merged into a
single class:
class A {
public:
virtual void foo() =3D 0;
virtual void bar() =3D 0;
};
class B : public virtual A {
public:
void foo();
};
class C : public virtual A {
public:
void bar() { foo(); }
};
class D : public B, public C {};
class E : public B, public C {
public:
void baz() { bar(); }
};
class F : public D, public E {
public:
void dosomething() { baz(); }
};
The problem here is that E::C::bar() supposes that foo() will invoke
its brother E::B::foo(), but it will not --- this code does not
compile because there's not one final overrider for foo(). It would
be necessary to define F::foo() and F::bar() for this code to compile,
but then neither would be able to distinguish foo() invocations from
the D or from the E branch.
Notice how this breaks code when it would not be needed. It may seem
crazy to make code like that but it is not that hard to be caught by
such problems if you really think on behavior, and abstraction is one
of the most important concepts on object orientation.
If it were possible to say "Ok, whoever inherits from me should not
take class A as virtual base", all this would be fixed, and it would
not be that hard to implement it this way. Even if someone did not
declare
class D : public B, public C, public final A {};
and the same for class E, class F could have indirectly inherited from
D and E, keeping a different sub-instance of A for each one, by
declaring:
class D_ : public D, public final A {};
class E_ : public E, public final A {};
class F : public D, public E {};
But this is current IMPOSSIBLE to do in C++, and I remember of really
NEEDING such a feature a long time ago, and having to do a HUGE
workaround because of not having this feature.
> Consider Java's "interface" class - that's just what a true abstract
> base class is, all interface and nothing that will ever require
> initialization.
Java does not have multiple inheritance of implementation, so the
problem I describe does not arise at all. If MI were ever introduced
into Java, the same problem of multiple implementations for the same
method would have to be considered and dealt with.
>> 2) It would also be good to allow a class to delegate virtual base
>> classes initialization to the first base class that would initialize
>> it. =3D20
>> class A { public: A(int); A(char *); };
>> class B : public virtual A { B(int i) : A(i) {} };
>> class C : public virtual A { C(char *c) : A(c) {} };
>> class D : public B, public C { D() : !A, B(1), C("") {} };
>> In this case, B would initialize A. The only additional restriction
>> is that neither A nor its members may be referred to before subobject
>> B is initialized.
> What does this buy you? You still need to know, when writing class D,
> about the initialization requirements of A (unless A is truly abstract
> <grin>): all this adds is the need to know that one of the intermediate
> classes can perform the initialization. D is still controlling the
> initialization, it's only doing it at one or more steps removed. This is=
> not an improvement in my opinion, as it increases rather than decreases
> the number of classes that must be understood in order to make changes to=
> ANY of these classes.
As I wrote in my original post, this additional construct is only
necessary to prevent already-developed code from breaking with this
change of behavior. It could also be defined that virtual base
classes are initialized by the first class that provides an
initializer in a depth-first search in the inheritance graph (this
would be quite straightforward to implement), but, once again, this
change would break existing code.
So I suggested an alternate construct to provide implementors some
means to centralize the maintenance for initialization of base
classes. That is, if, at some point in time, it is decided that A
should now be initialized with some arguments, you have to change only
one point in the program, instead of having to change every subclass
that could have inherited from it.
> Again, if A requires no explicit initialization, this problem goes away.
Of course. I agree that pure-virtual base classes solves this
problem, and I don't really think this additional complexity for
delaying initialization of virtual base classes is necessary, but it
would be useful for those cases where you, for some reason, don't want
the virtual base class to be completely abstract (such as a Java
interface).
--
Alexandre Oliva
mailto:oliva@dcc.unicamp.br
Universidade Estadual de Campinas, S=E3o Paulo, Brasil
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: Alexandre Oliva <oliva@dcc.unicamp.br>
Date: 1996/09/16 Raw View
I've been reading some messages on comp.lang.c++.moderated recently
about problems with virtual inheritance. The basic problem, as all of
you may know, is that if C inherits from B that inherits from virtual
A, C (and any of its subclasses) must be aware of A's virtual
inheritance and initialize it properly.
Another problem arises when two independent hierarchies, that both
inherit from virtual A, are merged in a single derived class: the
independence breaks. Although this is the basis for mix-in idioms, it
would be desirable to be able to control this behaviour.
I was thinking a way to prevent these problems. I hope this has not
been proposed and discarded before...
1) Class B, or class C, or any class which directly or indirectly
inherits from virtual A, could specify final A, meaning that no
derived class shall initialize A, or share subobject A implementation.
That is, for the following code snippet:
class A {};
class B : public virtual A {};
class C : public B, public final A {};
class D : public C {};
D would not have any responsibility for initializing A. For
subclasses of C, A would not be considered virtual at all.
class E : public virtual A {};
class F : public E, public final A {};
class G : public C, public F {};
In this case, class G would contain two sub-objects of type A, one
C::A and another F::A.=20
Maybe the keyword final (yes, I'm learning Java :-) is not
appropriate, but I could not think of a better one...
2) It would also be good to allow a class to delegate virtual base
classes initialization to the first base class that would initialize
it. =20
class A { public: A(int); A(char *); };
class B : public virtual A { B(int i) : A(i) {} };
class C : public virtual A { C(char *c) : A(c) {} };
class D : public B, public C { D() : !A, B(1), C("") {} };
In this case, B would initialize A. The only additional restriction
is that neither A nor its members may be referred to before subobject
B is initialized.
It might be argued that D could delegate the initialization by
default, but this would break existing code.
class E { public: E(A&); };
class F : public E, public B, public C {
F() : !A, E(A), B(1), C("") {}
};
This should not be valid because A has not been initialized yet. I
also agree that the syntax is rather ugly. I have also considered,
instead of !A, the following alternatives:
D(): A, ... // seems to mean the opposite, doesn't it?
D(): ~A, ... // is the destructor being invoked?=20
D(): ^A, ... // ^ denotes superclass
D(): B::A, ... // would look like you could write C::A too
D(): A(B::A), ... // ditto, and seems to be initializing A
It would be really good to be able to select which superclass should
initialize the virtual base class, but that would require much more
hacking in compilers, and can be achieved by changing the inheritance
declaration order anyway.
If this would pass, I'd also suggest that implicit constructors would
be generated so that virtual superclass initialization would be
automatically delegated to superclasses.=20
--=20
Alexandre Oliva
oliva@dcc.unicamp.br
Universidade Estadual de Campinas, S=E3o Paulo, Brasil
[ comp.std.c++ is moderated. To submit articles: try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]