[comp.lang.c++] multiple inheritance bug

lars@island.COM (Lars Nyman) (02/05/91)

I have a problem with multiple inheritance using virtual base classes, and
suspect it's a bug in the C++ compiler.

Three level hierarchy: Base, Derived, MostDerived.

class Base { ... }
class Derived : virtual Base { ... }
class MostDerived : Derived, Mixin { ... }

The problem occurs when executing inside Derived's constructor.   If
Derived() calls a function in Base, which in turns calls a virtual 
function implemented in Derived, the 'this' pointer will be incorrect
(see below code for more technical info).
Basically, makes it impossible to use multiple inheritance...

I have tested it under Sun C++ 2.0 and MPW C++ 3.1, both which are 
based on CFront 2.0.

Is this fixed in CFront 2.1 ?
Can somebody who have a C++ implementation based on CFront > 2.0 check
whether the same problem exist ?

// ---------- Begin Code --------------

#include <iostream.h>

class Shape {
 public:
    Shape() { cout << "Created Shape: " << (unsigned long) this << "\n"; }
    
    virtual void moveTo(int) { draw(); }
    virtual void draw() = 0;
};

class Circle : virtual public Shape {
 public:
    Circle() { moveTo(0); }

    void draw()
    {
	cout << "Circle drawing Shape: " << (unsigned long) (Shape *) this << "\n";
    }
};

class SomeMixin {
    char data[256];
};

class RedCircle : public Circle, public SomeMixin {
 public:
    RedCircle() { moveTo(0); }
    void draw()
    {
	cout << "RedCircle drawing Shape: " << (unsigned long) (Shape *) this << "\n";
    }
};


void main()
{
    cout << "********* Circle ***********\n";
    Circle c;

    cout << "********* RedCircle ********\n";
    RedCircle rc;
}

// -------------- End Code ---------------


When Circle is being constructed, it installs a vtbl for Shape.  However,
the vtbl Circle installs contains offsets as if Shape was part of a "pure"
Circle.  Thus, if a "pure" Circle is being constructed things work fine.
But, if the Circle being constructed is a part of something else, e.g. a 
RedCircle, which have multiple base classes, then the vtbl Circle installs
in its constructor for Shape is incorrect.

The vtbl Circle installs for Shape should be contain the same offsets
as the vtbl RedCircle installs for Shape in RedCircle's constructor,
but the vtbl should not contain the same functions as the vtbl RedCircle
installs (since when Circle's constructor is executing the object is
not yet a RedCircle).

beard@ux5.lbl.gov (Patrick C Beard) (02/06/91)

In article <3553@island.COM> lars@island.COM (Lars Nyman) writes:
#
#I have a problem with multiple inheritance using virtual base classes, and
#suspect it's a bug in the C++ compiler.

Don't be so quick to call this a bug.  It's actually a feature.  C++ does
not officially support the calling of virtual functions from a constructor.
The reason?  If you are in the base class constructor of a derived class,
and you call a virtual function, if it called a function that is defined in
the derived class, that function would be passed a pointer to an incompletely
constructed derived object.

#
#Three level hierarchy: Base, Derived, MostDerived.
#
#class Base { ... }
#class Derived : virtual Base { ... }
#class MostDerived : Derived, Mixin { ... }
#
#The problem occurs when executing inside Derived's constructor.   If
#Derived() calls a function in Base, which in turns calls a virtual 
#function implemented in Derived, the 'this' pointer will be incorrect
#(see below code for more technical info).
#Basically, makes it impossible to use multiple inheritance...

No it doesn't.  It just makes it impossible to have a constructor call
virtual functions of classes that are farther down in the inheritance
tree.  This shouldn't pose much of a problem.  My philosophy is that
constructors are for initialization.  They should perform no work.  That
is deferred until the object is completely initialized.  I find that this
makes my designs much more robust.

#
#I have tested it under Sun C++ 2.0 and MPW C++ 3.1, both which are 
#based on CFront 2.0.
#
#Is this fixed in CFront 2.1 ?
#Can somebody who have a C++ implementation based on CFront > 2.0 check
#whether the same problem exist ?

I would predict that this "feature" will remain in C++ always.

--
------------------------------------------------------------------
|  Patrick C. Beard, Software Engineer, Berkeley Systems, Inc.   |
|                    "Heroes of technology."                     |
|    beard@lbl.gov, d0346@applelink.apple.com (ATTN: Patrick)    |

lars@island.COM (Lars Nyman) (02/07/91)

In article <9592@dog.ee.lbl.gov> beard@ux5.lbl.gov (Patrick C Beard) writes:
#In article <3553@island.COM> lars@island.COM (Lars Nyman) writes:
##
##I have a problem with multiple inheritance using virtual base classes, and
##suspect it's a bug in the C++ compiler.
#
#Don't be so quick to call this a bug.  It's actually a feature.  C++ does
#not officially support the calling of virtual functions from a constructor.


ARM Section 10.9c, page 233:
"The first point at which C::f() can be called as f() is the first
 statement in the constructor C::C()".

Thus, C++ "officially" supports calling virtual functions from a constructor.


#The reason?  If you are in the base class constructor of a derived class,
#and you call a virtual function, if it called a function that is defined in
#the derived class, that function would be passed a pointer to an incompletely
#constructed derived object.


As my example stated, I do not call a virtual function from the constructor
of the base class.  I call a virtual function (indirectly) from the constructor
of the derived class.  The call of the virtual function in the derived class
occurs from function member (not constructor) of the base class.  The problem
is that the constructor of the derived class installs an incorrect vtable in
the base class part of itself, so the base class cannot transform itself to
a derived class (i.e. the offsets in the vtable is incorrect).

#
#Three level hierarchy: Base, Derived, MostDerived.
#
#class Base { ... }
#class Derived : virtual Base { ... }
#class MostDerived : Derived, Mixin { ... }
#

When Derived's constructor exits, MostDerived's constructor will
install a new (correct) vtable in the base class part of itself.
And things will work fine again.  Until, it is time to destruct the
MostDerived object.  Then again, Derived's destructor will install
an incorrect vtable in the Base part.

#My philosophy is that constructors are for initialization.  They should 
#perform no work.  That is deferred until the object is completely 
#initialized.  I find that this makes my designs much more robust.

To completely initalize an object, you might very well have to perfrom work.
And the right place to do that is of course in the constructor.
A destructor might want to notify others that it is about
to die - they might want to remove some references to the dying object.
Note, the object is not yet dead when we enter the destructor; it doesn't
cease to exist until the destructor exits.

Lars Nyman

jimad@microsoft.UUCP (Jim ADCOCK) (02/08/91)

In article <9592@dog.ee.lbl.gov> beard@ux5.lbl.gov (Patrick C Beard) writes:
|In article <3553@island.COM> lars@island.COM (Lars Nyman) writes:
|#
|#I have a problem with multiple inheritance using virtual base classes, and
|#suspect it's a bug in the C++ compiler.
|
|Don't be so quick to call this a bug.  It's actually a feature.  C++ does
|not officially support the calling of virtual functions from a constructor.
|The reason?  If you are in the base class constructor of a derived class,
|and you call a virtual function, if it called a function that is defined in
|the derived class, that function would be passed a pointer to an incompletely
|constructed derived object.

I mainly agree with Patrick -- but -- C++ *does* officially support 
the calling of virtual functions from a constructor.  ARM page 294:

"Member functions may be called in constructors and destructors.  This
implies that virtual functions may be called (directly or indirectly).
The function called will be the one defined in the constructor's 
(or destructor's) own class or its bases, but *not* in any 
function overriding it in a derived class....."

The reason for this behavior is very simple.  When in the base class
constructor, the object IS a base class object.  The object does not
become a derived class object until the derived class constructor begins to
run on it to make it a derived class object.  The object then IS a derived 
object until the mostderived class constructor starts to run on it,
making it a mostderived class object.  Thus, in any constructor, the object
being constructed IS exactly the type of the constructor's class -- at least
while in that constructor.  Destructors work exactly the same way.

Thus, virtual functions in constructors and destructors *are* officially
supported by the language, and work exactly the same way in constructors
and destructors as they work anywhere else in the language.  Rather, the
problem is that many people don't understand that the *type* of an 
object is dynamically changing as the object is run through each 
phase of construction and destruction.  Perhaps this example will
make things more clear:



extern "C"
{
	#include <stdio.h>
}

class base
{
public: 
	virtual void whatami() { printf("I'm a base.\n"); }
	base() { printf("In the base constructor "); whatami(); }
	~base() { printf("In the base destructor "); whatami(); }
};

class derived : public base
{
public: 
/*	virtual void whatami() { printf("I'm a derived.\n"); } */
	derived() { printf("In the derived constructor "); whatami(); }
	~derived() { printf("In the derived destructor "); whatami(); }
};

class mostderived : public derived
{
public: 
	virtual void whatami() { printf("I'm a mostderived.\n"); }
	mostderived() { printf("In the mostderived constructor "); whatami(); }
	~mostderived() { printf("In the mostderived destructor "); whatami(); }
};

main()
{
	mostderived object;

	return 0;
}

----

should print:

In the base constructor I'm a base.
In the derived constructor I'm a derived.
In the mostderived constructor I'm a mostderived.
In the mostderived destructor I'm a mostderived.
In the derived destructor I'm a derived.
In the base destructor I'm a base.

If it doesn't -- call your compiler vendor and complain.

jgro@lia (Jeremy Grodberg) (02/12/91)

In article <9592@dog.ee.lbl.gov> beard@ux5.lbl.gov (Patrick C Beard) writes:
>In article <3553@island.COM> lars@island.COM (Lars Nyman) writes:
>#
>#I have a problem with multiple inheritance using virtual base classes, and
>#suspect it's a bug in the C++ compiler.
>
>Don't be so quick to call this a bug.  It's actually a feature.  C++ does
>not officially support the calling of virtual functions from a constructor.

You are wrong, Mr. Beard.  ARM 12.7 clearly says 
    
    Member functions may be called in constructors and destructors.  This
    implies that virtual functions may be called (directly or indirectly).
    The function called will be the one defined in the constructor's
    (or destructor's) own class or its bases, but *not* any
    function overriding it in a derived class. [...] The effect of calling
    a pure virtual function directly or indirectly for the object being
    constructed from a constructor, except using explicit qualification,
    is undefined.

So, Mr. Beard, you are totally wrong in saying C++ does not offically
support the calling of virtual functions from a constructor.

However, Mr. Nyman's example code did involve calling a pure virtual
function indirectly for the object being constructed from a constructor,
without explicit qualification, so he has no reason to claim his sample
code should work.  

Also, since Mr. Nyman didn't include the output of his sample code in his
posting, it is hard to know if the problem was the code not working as
it should, or Mr. Nyman not understanding what the code should do.
Another test, without the pure virtual function, is called for before
calling this a bug.



-- 
Jeremy Grodberg      "I don't feel witty today.  Don't bug me."
jgro@lia.com          

niklas@appli.se (Niklas Hallqvist) (02/15/91)

In article <1991Feb11.213836.1840@lia> jgro@lia.com (Jeremy Grodberg) writes:
>In article <9592@dog.ee.lbl.gov> beard@ux5.lbl.gov (Patrick C Beard) writes:
>>In article <3553@island.COM> lars@island.COM (Lars Nyman) writes:
>>#
>>#I have a problem with multiple inheritance using virtual base classes, and
>>#suspect it's a bug in the C++ compiler.
>>
>>Don't be so quick to call this a bug.  It's actually a feature.  C++ does
>>not officially support the calling of virtual functions from a constructor.

>You are wrong, Mr. Beard.  ARM 12.7 clearly says 
>    
>    Member functions may be called in constructors and destructors.  This
>    implies that virtual functions may be called (directly or indirectly).
>    The function called will be the one defined in the constructor's
>    (or destructor's) own class or its bases, but *not* any
>    function overriding it in a derived class. [...] The effect of calling
>    a pure virtual function directly or indirectly for the object being
>    constructed from a constructor, except using explicit qualification,
>    is undefined.

I haven't got my ARM with my at this moment (but still both of my hands
keep writing :-), so I couldn't check out the context of the statement above.
I suppose the intended meaning is that you can't expect anything useful
to be done if you call a virtual function from a constructor, if the
function is declared as pure in the class where the constructor WAS FOUND.
Clearly, if the function was pure in one (or more) of this class' bases,
but defined in this class, the effect MUST be well-defined.  As english
is not my first language (neither Bjarne's), I have some difficulties in
understanding the last sentence of the quote above, maybe it has to be
clarified (Are you listening, ANSI-committe-members?).

>So, Mr. Beard, you are totally wrong in saying C++ does not offically
>support the calling of virtual functions from a constructor.

>However, Mr. Nyman's example code did involve calling a pure virtual
>function indirectly for the object being constructed from a constructor,
>without explicit qualification, so he has no reason to claim his sample
>code should work.  

The virtual function which was called, was pure in one of the BASES of
the class where the constructor was found, but DEFINED IN the class!
As I stated above, I think this is VERY legal.  Why shouldn't it be so?

>Also, since Mr. Nyman didn't include the output of his sample code in his
>posting, it is hard to know if the problem was the code not working as
>it should, or Mr. Nyman not understanding what the code should do.
>Another test, without the pure virtual function, is called for before
>calling this a bug.

He wrote a rather technical description of the bug (yes, I think it's one)
which seemed quite clear to me:  The vtable was incorrect when the
object were constructed (or destroyed).  He also made clear that he knew
that an object's dynamic type in a constructor is equal to the type (class)
where the constructor was found, and if he knew that it's easy to deduce
what he thought program should do.  As to the called for test,  I agree,
but even if that would work, I think it's a bug.  G++ fails with his
program but works if the virtual DERIVATION of Shape is made non-virtual,
as I've reported to FSF.

						Niklas

-- 
Niklas Hallqvist	Phone: +46-(0)31-40 75 00
Applitron Datasystem	Fax:   +46-(0)31-83 39 50
Molndalsvagen 95	Email: niklas@appli.se
S-412 63  GOTEBORG, Sweden     mcsun!sunic!chalmers!appli!niklas