asc@concurrent.co.uk (Andy Chittenden) (01/24/90)
// The following program demonstrates a problem with virtual inheritence: class base { public: base(); base(unsigned long& p); }; class derived : public virtual base { public: derived(); derived(unsigned long& p); }; derived::derived(unsigned long& p) : base(p) {} class mostderived : public virtual derived { public: mostderived(); mostderived(unsigned long& p); }; mostderived::mostderived(unsigned long& p) : derived(p) {} main() { unsigned long counter; mostderived x(counter); } // The problem encountered is that without virtual inheritence the // constructor for mostderived with parameter p does call derived's // constructor with parameter p and derived's constructor with parameter p // does call upon base's constructor with parameter. This is exactly how // you would expect it to work. However, with virtual inheritence, it // doesn't work like this at all. In order to prevent the constructor for // base being called twice when used with multiple inheritence, // mostderived's constructor with parameter p calls upon base's constructor // WITHOUT parameter p. This means that the base part of mostderived is // not constructed properly. The code generated is ( with much // editting): // struct mostderived *__ct__11mostderivedFRUl (__0this , // __0base , __0derived , __0p ) // struct mostderived *__0this ; // struct base *__0base ; // struct derived *__0derived ; // unsigned long *__0p ; // #line 25 "0708067014.C" // { if (__0this || // (__0this = (struct mostderived *)__nw__FUi (sizeof(struct mostderived)))) // ( (__0this -> Pbase= ((__0base == 0 )?( (__0base = // #line 25 "0708067014.C" // (((struct base *)((((char *)__0this ))+ 12)))), //->>>>>> __ct__4baseFv ( ((struct base *)((((char *)__0this ))+ 12))) ) // :__0base )), (__0this -> Pderived= ((__0derived == // #line 25 "0708067014.C" // 0 )?( (__0derived = (((struct derived *)((((char *)__0this ))+ 8)))), // __ct__7derivedFRUl ( ((struct derived *)((((char *)__0this ))+ 8)), // __0base , __0p ) ) // #line 25 "0708067014.C" // :__0derived ))) ; // // #line 25 "0708067014.C" // return __0this ; // } // The line marked with ->>>>>> is the line that calls upon base's no // parameter constructor. // One way around this is to implement the constructor differently as: // mostderived::mostderived(unsigned long& p) : derived(p), base(p) {} // However, this presents a problem with regard to impact of change. If // I now split base into two for some reason: // // class mostbase { // public: // mostbase(); // mostbase(unsigned long& p); // }; // and // class base : public virtual mostbase { // .... // }; // // I would now need to change the implementation of mostderived // constructor to: // mostderived::mostderived(unsigned long& p) : derived(p), // base(p), // mostbase(p) {} // I would also need to change derived's constructor as well. // This seems to go against the object-oriented principle of keeping // the impact of a change to the class being changed if the interface // remains the same. // Cfront could have generated pointers to pointers in the above code // to prevent base being constructed with the "wrong" constructor. In // other words (in simplified pseudo C++): // mostderived::mostderived(mostderived* this, base** x, derived** y, // unsigned long& p) // { if (this != NULL || this = new mostderived) { // *y = (*y == NULL) ? derived::derived((char*) this + 8, x, p) // : *y ; // *x = (*x == NULL) ? base::base((char*) this + 12) : *x; // /* the constructor on this line won't get // called if derived's constructor // runs base's constructor */ // this->Pbase = *x; // this->Pderived = *y; // return this; // } // (Aside: the above code is identical to the AT&T generated code up // above except that the parameters to the routines are pointers to // pointers rather than pointers - obviously the *y and *x // expressions would be y and x respectively in AT&T's generated code. // This should help your understanding of some AT&T's generated code!). // Any comments?, Andy