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                             ]