keith@csli.Stanford.EDU (Keith Nishihara) (12/05/89)
When a class has a constructor and virtual functions, cfront1.2 generates
code within the constructor function to set up the virtual function pointers.
If a class is derived from such a base class, and one of the virtual
functions is called from the base class constructor, the wrong virtual
function is called, since during the execution of the base class constructor,
the virtual function pointer table is set up as for the base class,
and is not changed to show the derived class virtual functions until
the derived class constructor is entered.
Here is a real case:
I have a hierarchical graphical editor in which the user
manipulates prototypical objects in a layout. There are several
types of primitive proto objects, and also compound proto objects
(which represent occurrences of other layouts included within a
higher level layout). These proto objects are derived from a base
class `proto'. In order to support execution, part of the objects
state is represented as an `instance' class; primitive proto
classes create instance classes of themselves for each occurrence
of a compound proto object representing the layout including the
primitive proto.
The implementation of this requires that each proto class define
a virtual function Instantiate(context), which creates an instantiation
in the given context. *All* derived classes are required to have
at least one instance, defined within a context called top_level.
The most natural way to achieve this is to have the constructor
for the base class call the virtual function Instantiate().
However, at the time that this virtual function is called, the virtual
function table is set up as if for the base class, and not for the
derived class.
class proto
{
public:
proto() { ... Instantiate(top_level); ... }
virtual void Instantiate(context) { } // Empty.
...
};
class type_a_proto : public proto
{
public:
virtual void Instantiate(context) { <create instance> }
};
What happens is that the empty base class Instantiate function
is called instead of the type_a_proto::Instantiate function when
a new type_a_proto is created.
(The fix, of course, is to call the Instantiate function from each
derived constructor, instead of the base class. The problem is that
it is then not possible to derive further sub classes of a derived type,
as the intermediate class will create unwanted instantiations of
themselves as their intermediate constructor functions are called.)
Is this reasonable semantics for virtual function calling? I know that it
is somewhat bogus to do anything with the derived object before its
constructor has been called, but I do not find anything which says
that it is illegal. In this case, my virtual function calls are not
actually operating on the derived object, but are rather creating a
parallel structure in a different context.
Do other C++ compilers exhibit the same behaviour?
Neil/. Neil%teleos.com@ai.sri.com ...decwrl!argosy!teleos!neilmadany@m.cs.uiuc.edu (12/06/89)
/* Written 3:07 pm Dec 4, 1989 by keith@csli.Stanford.EDU in m.cs.uiuc.edu:comp.lang.c++ */ /* ---------- "Chameleon objects (calling virtual" ---------- */ >> (The fix, of course, is to call the Instantiate function from each >> derived constructor, instead of the base class. The problem is that >> it is then not possible to derive further sub classes of a derived type, >> as the intermediate class will create unwanted instantiations of >> themselves as their intermediate constructor functions are called.) Not quite true. You can define two constructors for each class, with different numbers of parameters. For example: SubClass::SubClass( parameters ) : Class( parameters, 0 ) { Instantiate(context); ... } and SubClass::SubClass( parameters, int ) : Class( parameters, 0 ) { ... } Then the compiler could distinguish between the two constructors. For each class, use the constructor without the extra int when creating objects of that class, and use the constructor with the int when calling up the constructor chain. Now you have a way to avoid calling some function twice, you can insert code that should only be called once for an object in the constructor used for object creation, and you can subclass to your hearts content. >> Is this reasonable semantics for virtual function calling? I know that it >> is somewhat bogus to do anything with the derived object before its >> constructor has been called, but I do not find anything which says >> that it is illegal. In this case, my virtual function calls are not >> actually operating on the derived object, but are rather creating a >> parallel structure in a different context. This semantics for calling virtual functions in constructors seems reasonable to me, though it can be frustrating in cases similar to the one you described. It makes sense that an object belongs to the class of a currently executing constructor or destructor and not to any subclass. -peter madany
roger@procase.UUCP (Roger H. Scott) (12/07/89)
In article <11266@csli.Stanford.EDU> Neil%teleos.com@ai.sri.com writes: >When a class has a constructor and virtual functions, cfront1.2 generates >code within the constructor function to set up the virtual function pointers. >If a class is derived from such a base class, and one of the virtual >functions is called from the base class constructor, the wrong virtual >function is called, since during the execution of the base class constructor, >the virtual function pointer table is set up as for the base class, >and is not changed to show the derived class virtual functions until >the derived class constructor is entered. This is one of the really nasty unsolved problems in C++. As much as I hate this behavior, I have to admit that it is really the only correct behavior for virtual functions. The function that is called as a result of a virtual call is determined by the dynamic type of the object, and it seems pretty clear that the dynamic type of an object executing T::T() is T, regardless of any subclassing. The analogous thing holds true during destructors - during the execution of T::~T() the dynamic type of an object is demoted to T. Perhaps what is needed here is a "finalization" function that is automatically invoked by the compiler immediately after normal construction of an object. The programmer could declare this function to be virtual in the base class and then redefine it in derived classes to "finalize" the object in the appropriate way(s). The syntax below is not a suggestion: class Base { public: Base(); // do invariant Base stuff virtual !Base(); // [finalizer] do Base variant of finalization ... }; class Derived : public Base { public: Derived(); // do invariant Derived stuff !Derived(); // do Derived variant of finalization ... }; ... Base *p = new Derived; // p = (tmp = new Derived, tmp->!Base(), tmp) ... I'm not at all thrilled with the prospect of Yet Another Language Extension, so here's an approach that works in 2.0 C++ as-is: // constructors are private so you won't "forget" to finalize ... class Base { Base(); // do invariant Base stuff protected: virtual Base *Finalize(); // do Base variant of finalization public: static Base *New() {return (new Base)->Finalize();} ... }; class Derived : public Base { Derived(); // do invariant Derived stuff protected: Base *Finalize(); // do Derived variant of finalization public: static Derived *New() {return (Derived *)(new Derived)->Finalize();} ... }; ... Base *p = Derived::New(); ... [Digression #1] By the way, an advantage to using static member functions for public construction rather than C++ constructors is that static member functions are [more nearly] first-class entities in C++ than constructors - you can take their address and treat (pointers to) them as variables. Such is not the case with T::T(). typedef Base *BaseMaker(); // Create a Base (or a subclass of Base) and use it ... void makeABaseAndDoSomethingWithIt(BaseMaker *makebase) { ... Base *b = (*makebase)(); ... } void foo() { makeABaseAndDoSomethingWithIt(&Base::New); // The cast in the following line should not be necessary - // see (***) note following. makeABaseAndDoSomethingWithIt((BaseMaker *)&Derived::New); } [Digression #2 - for Language Lawyers only] (***) Note: "Derived *(*)()" [pointer to function returning pointer to Derived] should be type compatible with "Base *(*)()" [pointer to function returning pointer to Base]. These types were compatible in 1.2. AT&T maintains that these are incompatible for the same reasons that "Derived **" is incompatible with "Base **", but the two cases are *not* analogous - there is no danger of "unsafe" things happening in the former case. It is not as if you could assign to the "object" pointed to by a pointer-to-function and thus alter what will be returned when that p-to-f is called through. Genuine unsafe example: Base *IPointToABase = new Base; void f(Base **pp) { *pp = IPointToABase; // BECAUSE YOU *CAN* DO THIS ... } Derived *IPointToADerived = new Derived; void g() { Derived **mypp = &IPointToADerived; f(mypp); // ... YOU *CAN'T* DO THIS, FOR FEAR OF ... Derived *dp = *mypp; // ... GETTING A "Base *" HERE! } Bogus pseudo-analogy: Base *IReturnABase() {return new Base;} void f(Base *(*pf)()) { *pf = IReturnABase; // BECAUSE YOU *CAN'T* DO THIS ... ... } Derived *IReturnADerived() {return new Derived;} void g() { Derived *(*mypf)() = &IReturnADerived; f(mypf); // ... YOU *SHOULD* BE ABLE TO DO THIS, SECURE // IN THE KNOWLEDGE THAT ... Derived *dp = (*mypf)(); // ... THIS CAN'T YIELD A "Base *"! }
jimad@microsoft.UUCP (Jim Adcock) (12/08/89)
In article <11266@csli.Stanford.EDU> Neil%teleos.com@ai.sri.com writes: >When a class has a constructor and virtual functions, cfront1.2 generates >code within the constructor function to set up the virtual function pointers. >If a class is derived from such a base class, and one of the virtual >functions is called from the base class constructor, the wrong virtual >function is called, since during the execution of the base class constructor, >the virtual function pointer table is set up as for the base class, >and is not changed to show the derived class virtual functions until >the derived class constructor is entered. > >Do other C++ compilers exhibit the same behaviour? > >Neil/. Neil%teleos.com@ai.sri.com ...decwrl!argosy!teleos!neil One would hope so, since this seems to be the behavior described in section 12.7 page 83 of the C++ Reference Manual (short quote:) "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 redefining it in a derived class. This ensures that unconstructed objects will not be accessed during construction or destruction. For example: ...." --- Given a class Base, and a class Derived, an object created of class Derived is done so by first invoking a Base constructor on the object, at which time the object is a Base, then by invoking a Derived constructor on the object, at which time it has become a Derived. [Likewise in multiple inheritence, a MIDerived starts off solely as a Base1 during the Base1 construction phase, is solely a Base2 during the Base2 construction phase, and only becomes a MIDerived (and thus also a Base1 and Base2) during the MIDerived construction phase.] This description is also true (in reversed order) during destruction. After the Derived destructor is called, the object has reverted to only being a Base, after the Base desctructor is called it isn't (hardly(*)) anything. (hardly(*)) == fudge factor to account for static class member functions operator new and operator delete.
vinoski@apollo.HP.COM (Stephen Vinoski) (12/08/89)
In article <11266@csli.Stanford.EDU> Neil%teleos.com@ai.sri.com writes: >If a class is derived from such a base class, and one of the virtual >functions is called from the base class constructor, the wrong virtual >function is called, since during the execution of the base class constructor, >the virtual function pointer table is set up as for the base class, >and is not changed to show the derived class virtual functions until >the derived class constructor is entered. From Lippman's C++ Primer, page 352: "There are three cases in which an invocation of a virtual function is resolved statically at compile time: 1. When a virtual function is invoked through an object of the class type. . . . 2. When a virtual function is explicitly invoked through a pointer or reference using the class scope operator. . . . 3. When a virtual function is invoked within either the constructor or the destructor of a base class. In both cases, the base class instance of the virtual function is called since the derived class object is either not yet constructed or already destructed." I don't see how it could be done any other way. -steve | Steve Vinoski | Hewlett-Packard Apollo Div. | ARPA: vinoski@apollo.com | | (508)256-6600 x5904 | Chelmsford, MA 01824 | UUCP: ...!apollo!vinoski | | "My second wife isn't even born yet." |
strick@osc.COM (henry strickland) (12/12/89)
In article <47479d37.12160@espol> roger@procase.UUCP (Roger H. Scott) writes: > >[Digression #2 - for Language Lawyers only] >"Derived *(*)()" [pointer to function returning pointer to Derived] >should be type compatible with "Base *(*)()" [pointer to function >returning pointer to Base]. These types were compatible in 1.2. In 1.2 you did get away with this, because there were no multiply inherited bases buried within an object -- all the bases overlaid each other, starting at the same address. I don't think this is for Language Lawyers only -- all 2.0 users need to understand [it took me a while to realize it, and I'm still finding cases where I had missed one of the aftershocks of it] that all pointers (of different pointer-to-base-class types) to an object do *not* necessarily contain the same absolute value. The following program demonstrates a simple example where (Base*)&x and (Derived*)&x are not the same absolute value, for a Derived x. ============================================== strick@gwarn /tmp 445 % cat > ex.c extern "C" void printf( char const*, ... ); class Base { int b; }; class Other { int o; }; class Derived : public Other, public Base { int d; }; Derived x; main(int, char*[] ) { ::printf("%x %x\n", (Base*) &x, (Derived*) &x); return 0; } strick@gwarn /tmp 446 % CC2 ex.c CC2 ex.c: /usr/local/bin/gcc ex.c -lC2 strick@gwarn /tmp 447 % a.out 20094 20090 strick@gwarn /tmp 448 % ============================================== If you add the following lines to the end of the above code, you have a counterexample, that would be wrong if it would compile. typedef Base *(*PFPBase)(); typedef Derived *(*PFPDerived)(); Base* BaseOfX() { return &x; } void func() { PFPBase f= BaseOfX; Base* mumble= (*f)(); PFPDerived g= BaseOfX; // error: // bad initializer type Base *(*)() // for g ( PFPDerived expected) // the above would lead to the next line not // working right, if the compiler allowed // it, because fleezle would get the absolute // value (Base*)&x rather than // the absolute value (Derived*)&x . Derived* fleezle= (*g)(); } =============================================== Language Laypeople will be bit by all this on simple chains like (Base*) (void*) &x; // ERROR NOT CAUGHT BY COMPILER // (because you used casting) which could easily happen if you store the address of your x into a (void*)-collection object, then take it from the collection and cast it to a (Base*). This will NOT work with multiple inheritance. =============================================== strick@osc.com uunet!osc!strick ( formerly strick@gatech.edu )
dove@joker.uucp (Webster &) (12/17/89)
In article <47479d37.12160@espol> roger@procase.UUCP (Roger H. Scott) writes:
From: roger@procase.UUCP (Roger H. Scott)
Subject: Re: Chameleon objects (calling virtual functions from constructors)
Date: 7 Dec 89 09:01:00 GMT
Perhaps what is needed here is a "finalization" function that is automatically
invoked by the compiler immediately after normal construction of an object.
The programmer could declare this function to be virtual in the base class
and then redefine it in derived classes to "finalize" the object in the
appropriate way(s).
Yes Please!! We should have a means of automatically performing
"final integration" of objects after construction is complete.
--
Dr. Webster Dove
Special Computing Applications
Advanced Technology Engineering
Sanders Associates (a Lockheed Company)
uunet!rocket!dovebrad@sqwest.sq.com (Brad Might) (12/20/89)
> From: strick@osc.COM (henry strickland) > > The following program demonstrates a simple example where > (Base*)&x and (Derived*)&x are not the same absolute value, > for a Derived x. > ... code example Can I do the following with multiple inheritance then ? class Derived : public Other, public Base ... some fn Foo returns *Other. can i do Derived *d = (Derived *)Foo() ; as I could if Derived was derived strictly from Other ? -- Brad Might brad@sq.com (brad@sq ?) SoftQuad West brad!sq!utzoo!... (604) 585-1999
roger@decvax.UUCP (Roger H. Scott) (01/04/90)
In article <1727@osc.COM> strick@osc.com (henry strickland) writes: >In article <47479d37.12160@espol> roger@procase.UUCP (Roger H. Scott) writes: >> >>[Digression #2 - for Language Lawyers only] >>"Derived *(*)()" [pointer to function returning pointer to Derived] >>should be type compatible with "Base *(*)()" [pointer to function >>returning pointer to Base]. These types were compatible in 1.2. > >In 1.2 you did get away with this, because there were no >multiply inherited bases buried within an object -- all the >bases overlaid each other, starting at the same address. > >... >The following program demonstrates a simple example where >(Base*)&x and (Derived*)&x are not the same absolute value, >for a Derived x. ============================================== extern "C" void printf( char const*, ... ); class Base { int b; }; class Other { int o; }; class Derived : public Other, public Base { int d; }; Derived x; main(int, char*[] ) { ::printf("%x %x\n", (Base*) &x, (Derived*) &x); return 0; } typedef Base *(*PFPBase)(); typedef Derived *(*PFPDerived)(); Base* BaseOfX() { return &x; } void func() { PFPBase f= BaseOfX; Base* mumble= (*f)(); PFPDerived g= BaseOfX; // error: // bad initializer type Base *(*)() // for g ( PFPDerived expected) // the above would lead to the next line not // working right, if the compiler allowed // it, because fleezle would get the absolute // value (Base*)&x rather than // the absolute value (Derived*)&x . ### Of course this is wrong - you are going the wrong way! I claimed that "Derived *(*)()" should be compatible with "Base *(*)()", not vice versa as you are doing here. With this clarification do you still maintain that I am wrong and that the 2.0 behavior is right and/or necessary? Derived* fleezle= (*g)(); } >
shopiro@alice.UUCP (Jonathan Shopiro) (01/06/90)
In article <64@espol.decvax.UUCP>, roger@decvax.UUCP (Roger H. Scott) writes: > In article <1727@osc.COM> strick@osc.com (henry strickland) writes: > >In article <47479d37.12160@espol> roger@procase.UUCP (Roger H. Scott) writes: > >> > >>[Digression #2 - for Language Lawyers only] > >>"Derived *(*)()" [pointer to function returning pointer to Derived] > >>should be type compatible with "Base *(*)()" [pointer to function > >>returning pointer to Base]. These types were compatible in 1.2. > > > >In 1.2 you did get away with this, because there were no > >multiply inherited bases buried within an object -- all the > >bases overlaid each other, starting at the same address. > > > >... class Base { int b; }; class Other { int o; }; class Derived : public Other, public Base { int d; }; Derived x; // Aren't these typedefs easier to read than what you wrote? typedef Base* FPBase(); typedef Derived* FPDerived(); Base* BaseOfX() { return &x; } Derived* DerivedOfX() { return &x; } void func() { FPBase* f = &BaseOfX; // okay FPDerived* g = &DerivedOfX; // okay Base* mumble = (*f)(); // okay Base* fleezle = (*g)(); // works okay, takes care of offset // compare the generated code with the previous // If I remember correctly, this is what you'd like to be able to do. f = &DerivedOfX; // or even harder ... f = g; // While this is apparently type-safe, the compiler can't do it because // FPBase and FPDerived are used differently, as the above example shows. // The only way this could be done is for the compiler to lay down a little // function that did the offset adjustment and then use the address of // that. The first assignment to f could be done with a hidden static // function, but for the second assignment it would be much harder // (suppose the value of g changes later, suppose func is called // recursively). } -- Jonathan Shopiro AT&T Bell Laboratories, Warren, NJ 07060-0908 research!shopiro (201) 580-4229