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