vaughan%cadillac.cad.mcc.com@MCC.COM (Paul Vaughan) (01/12/90)
Here are a couple of bugs that pop up in long, obscure inheritance hierarchies. The code generated from the following source prints an incorrect result. The NamedObject destructor and constructor print the "this" pointer, but note that a NamedObject is destructed that was not constructed. Uncommenting out the destructor of IntegerAt produces correct behavior. #include <stream.h> class NameSpaceTable; class Object { public: Object() {}; virtual ~Object() {cout << "~Object()\n";} }; class NamedObject: public Object { public: NamedObject(const char* n = 0); ~NamedObject(); virtual const char* Name() const; private: const char* name=0; }; const char* NamedObject::Name() const { if (name) return name; else return "?????"; } NamedObject::NamedObject(const char* n = 0): () { name = n; cout << (int) this << " Constructor NamedObject() " << (int)name << "\n"; } NamedObject::~NamedObject() { cout << (int) this << " ~NamedObject() " << (int)name << "\n"; } class Entity: public NamedObject { public: Entity(const char* n=0,Entity* o=0,NameSpaceTable* nspace=0, int namespaceSize = 100) : NamedObject(n) {}; ~Entity() { cout << "~Entity()\n";} }; class Attribute : public Entity { public: Attribute(const char* n=0,Entity* o=0,NameSpaceTable* nspace=0) : Entity(n,o,nspace,0) {}; ~Attribute() {cout << "~Attribute()\n";} }; class IntegerAt : public Attribute { public: IntegerAt(const char* n=0,Entity* o=0,NameSpaceTable* nspace=0) : Attribute(n,o,nspace) {}; // ~IntegerAt() {cout << "~IntegerAt()\n";} }; class Behavior : public Entity { public: Behavior(const char* n= 0, Entity* o = 0, NameSpaceTable* ns = 0, int s = 0) : Entity (n, o, ns, s) {}; virtual ~Behavior() {cout << "~Behavior()\n";} }; class AttributeBehavior : public Behavior { public: AttributeBehavior(const char* n = 0, Entity* o = 0, NameSpaceTable* ns = 0, int s = 0) : Behavior (n, o, ns, s) {} ~AttributeBehavior() {cout << "~AttributeBehavior()\n";} }; class InterAtt : public AttributeBehavior { public: ~InterAtt() {cout << "~InterAtt()\n";} // ~InterAtt() {cout << "~InterAtt()\n";} }; class IgIntAtt : public InterAtt, public IntegerAt { public: IgIntAtt(char* n, Entity* o): IntegerAt(n, o) {}; ~IgIntAtt(); const char* Name() const {} }; IgIntAtt::~IgIntAtt() {cout << "~IgIntAtt()\n";} main() { IgIntAtt* iat = new IgIntAtt("fred", 0); delete iat; } [2.234]puma) !g g++-1.36.2 -v -g -o AtHier1 AtHier1.cc g++ version 1.36.2- (based on GCC 1.36) /usr/local/gnu/1.36.2/ylib/gcc-cpp -+ -v -undef -D__GNUC__ -D__GNUG__ -D__cplusplus -Dmc68000 -Dsun -Dunix -D__mc68000__ -D__sun__ -D__unix__ -D__HAVE_68881__ -Dmc68020 AtHier1.cc /usr/tmp/cca06057.cpp GNU CPP version 1.36 /usr/local/gnu/1.36.2/ylib/gcc-cc1plus /usr/tmp/cca06057.cpp -quiet -dumpbase AtHier1.cc -g -version -o /usr/tmp/cca06057.s GNU C++ version 1.36.2- (based on GCC 1.36) (68k, MIT syntax) compiled by GNU C version 1.36. default target switches: -m68020 -mc68020 -m68881 -mbitfield /usr/local/gnu/1.36.2/ylib/gcc-as -mc68020 -o AtHier1.o /usr/tmp/cca06057.s /usr/local/gnu/1.36.2/ylib/gcc-ld -o AtHier1 -e start -dc -dp -Bstatic /lib/crt0.o /lib/Mcrt1.o AtHier1.o -lg++ /usr/local/gnu/1.36.2/ylib/gcc-gnulib -lg -lc [2.235]puma) !A AtHier1 141732 Constructor NamedObject() 11718 141724 Constructor NamedObject() 0 ~IgIntAtt() ~Attribute() ~Entity() 141740 ~NamedObject() 0 ~Object() ~InterAtt() ~AttributeBehavior() ~Behavior() ~Entity() 141724 ~NamedObject() 0 ~Object() [2.236]puma) ------------------------------------------------------------------ In this second example, there are again two NamedObjects in the final IServiceDelayAt object. The Name() method (defined first on NamedObject) is therefore ambiguous in the IServiceDelayAt, except that I have defined an IServiceDelayAt::Name() to call the ServiceDelayAt::Name(). Note that the "this" pointer printed out by ServiceDelayAt::Name() is equal to the "this" pointer printed by IServiceDelayAt::Name(). Those pointers should not be equal. #include <stream.h> class NameSpaceTable; class Object { public: Object() {}; virtual ~Object() {cout << "~Object()\n";} }; class NamedObject: public Object { public: NamedObject(const char* n = 0); ~NamedObject(); virtual const char* Name() const; private: const char* name=0; }; const char* NamedObject::Name() const { if (name) return name; else return "?????"; } NamedObject::NamedObject(const char* n = 0): () { name = n; cout << (int) this << " NamedObject() Name() = " << Name() << "\n"; } NamedObject::~NamedObject() { cout << (int) this << " ~NamedObject() " << (int)name << "\n"; } class Entity: public NamedObject { public: Entity(const char* n=0,Entity* o=0,NameSpaceTable* nspace=0, int namespaceSize = 100) : NamedObject(n) {}; ~Entity() { cout << "~Entity()\n";} }; class Attribute : public Entity { public: Attribute(const char* n=0,Entity* o=0,NameSpaceTable* nspace=0) : Entity(n,o,nspace,0) {}; ~Attribute() {cout << "~Attribute()\n";} }; class IntegerAt : public Attribute { public: IntegerAt(const char* n=0,Entity* o=0,NameSpaceTable* nspace=0) : Attribute(n,o,nspace) {}; // ~IntegerAt() {cout << "~IntegerAt()\n";} }; class Behavior : public Entity { public: Behavior(const char* n= 0, Entity* o = 0, NameSpaceTable* ns = 0, int s = 0) : Entity (n, o, ns, s) {}; virtual ~Behavior() {cout << "~Behavior()\n";} }; class AttributeBehavior : public Behavior { public: AttributeBehavior(const char* n = 0, Entity* o = 0, NameSpaceTable* ns = 0, int s = 0) : Behavior (n, o, ns, s) {} ~AttributeBehavior() {cout << "~AttributeBehavior()\n";} }; class InterAtt : public AttributeBehavior { public: InterAtt() { cout << "InterAtt() Name() = " << Name() << "\n"; }; // ~InterAtt() {cout << "~InterAtt()\n";} }; class ServiceDelayAt : public IntegerAt { public: ServiceDelayAt() : IntegerAt("delay") { cout << "ServiceDelayAt() Name() = " << Name() << "\n"; }; virtual const char* Name() const { cout << "<" << (int) this << " ServiceDelayAt::Name>"; return NamedObject::Name(); } ~ServiceDelayAt() {}; }; class IServiceDelayAt : public InterAtt, public ServiceDelayAt { public: IServiceDelayAt(); ~IServiceDelayAt(); virtual const char* Name() const { cout << "<" << (int) this << " IServiceDelayAt::Name>"; return ServiceDelayAt::Name(); } }; IServiceDelayAt::~IServiceDelayAt() {cout << "~ServiceDelayAt()\n";} IServiceDelayAt::IServiceDelayAt() { cout << "IServiceDelayAt()Name() = " << Name() << "\n"; } main() { IServiceDelayAt* delay = new IServiceDelayAt; cout << "\n"; cout << "delay->Name() = " << delay->Name() << "\n\n"; delete delay; } [2.244]puma) !g g++-1.36.2 -v -g -o AtHier1 AtHier.cc g++ version 1.36.2- (based on GCC 1.36) /usr/local/gnu/1.36.2/ylib/gcc-cpp -+ -v -undef -D__GNUC__ -D__GNUG__ -D__cplusplus -Dmc68000 -Dsun -Dunix -D__mc68000__ -D__sun__ -D__unix__ -D__HAVE_68881__ -Dmc68020 AtHier.cc /usr/tmp/cca06094.cpp GNU CPP version 1.36 /usr/local/gnu/1.36.2/ylib/gcc-cc1plus /usr/tmp/cca06094.cpp -quiet -dumpbase AtHier.cc -g -version -o /usr/tmp/cca06094.s GNU C++ version 1.36.2- (based on GCC 1.36) (68k, MIT syntax) compiled by GNU C version 1.36. default target switches: -m68020 -mc68020 -m68881 -mbitfield /usr/local/gnu/1.36.2/ylib/gcc-as -mc68020 -o AtHier.o /usr/tmp/cca06094.s /usr/local/gnu/1.36.2/ylib/gcc-ld -o AtHier1 -e start -dc -dp -Bstatic /lib/crt0.o /lib/Mcrt1.o AtHier.o -lg++ /usr/local/gnu/1.36.2/ylib/gcc-gnulib -lg -lc [2.245]puma) !A AtHier1 141724 NamedObject() Name() = ????? InterAtt() Name() = ????? 141732 NamedObject() Name() = delay ServiceDelayAt() Name() = <141732 ServiceDelayAt::Name>delay IServiceDelayAt()Name() = <141724 IServiceDelayAt::Name><141724 ServiceDelayAt::Name>????? delay->Name() = <141724 IServiceDelayAt::Name><141724 ServiceDelayAt::Name>????? ~ServiceDelayAt() ~Attribute() ~Entity() 141732 ~NamedObject() 10881 ~Object() ~AttributeBehavior() ~Behavior() ~Entity() 141724 ~NamedObject() 0 ~Object() [2.246]puma) ------------------------------------------------------------- Now for a note about dubious compiler behavior. If you remove both the ServiceDelayAt::Name() and IServiceDelayAt::Name() declarations (and definitions), the Name() method on IServiceDelayAt is ambiguous. When the compiler finds something like ((IServiceDelayAt* ) p)->Name(), it prints the error message AtHier.cc:116: member `Name' belongs to MI-distinct base classes `NamedObject' which is certainly correct. However it does not complain at the time of the IServiceDelayAt class definition. If you have a ServiceDelayAt::Name(), but no IServiceDelayAt::Name(), the compile warns that AtHier.cc:93: ambiguous virtual function `const char *ServiceDelayAt::Name ()const ' AtHier.cc:24: ambiguating function `const char *NamedObject::Name ()const ' (joined by type `IServiceDelayAt') when compiling the IServiceDelayAt class definition. This is also certainly correct. In my opinion, this sort of message should have been printed when compiling the IServiceDelayAt class definition in the first case also, because the situation is equally ambiguous. For instance, it is not clear at all which NamedObject would be accessed by ((Attribute*) p)->Name(), but this does not result in any error message. It can produce counterintuitive results, however. -------------------------------------------- Finally, a bit of explanation for weird code. First, we were never able to isolate our (perceived) compiler bugs with using virtual base classes. We last decided to just work around having duplicate structures in our objects. At this point, I'm beginning to think we should have worked more on isolating the virtual base class problems. I usually try to avoid sending such large pieces of source to illustrate our compiler bugs, but I am finding that more and more difficult. In working with this example, it took a long time to figure out what was important and finally get it down into a single file of source code that we can send out without violating our code hoarding secrecy doctrines. I expect that this process is fairly essential in many cases, though, because it is this process that usually verifies that it is a compiler bug, and not one of our own breeds. The only alternative that I know that still gives you a compilable and executable example would be to tar up hundreds of megabytes of code/makefiles/libraries. Perhaps if we can ever manage to procure the services of your new support company we will have an easier time of it?