Path: utzoo!utgpu!jarvis.csri.toronto.edu!cs.utexas.edu!tut.cis.ohio-state.edu!husc6!genrad!charlie From: charlie@genrad.com (Charlie D. Havener) Newsgroups: comp.lang.c++ Subject: VIRTUAL-NESS LOST - A C++ mystery involving funcs returning objects not ptrs Keywords: virtual C++ overloaded operator interpreter Message-ID: <33629@genrad.UUCP> Date: 9 Mar 90 19:19:31 GMT Sender: news@genrad.UUCP Lines: 293 WHEN IS "VIRTUAL-NESS LOST"? A puzzle involving overloaded operator functions, multiple inheritance, and functions returning class objects, instead of pointers or references to an object. ------------------------ SYNOPSIS: If you return a derived class object from a function, can you then invoke a virtual function on it and expect C++ to find the right one? Class Base { virtual print(); } Class Der : public Base { virtual print(); } Base func() { Der x; return x; } main() { func().print(); } // should it use the Base or Der print? // cfront uses Der print, Zortech 2.06 uses Base print if it was main() { Base t; t = func(); t.print(); } // Clearly it should use the Base print ------------------------- I can find no definitive answer in any of my books or the C++ reference manual. Section 12.2 in the Ref manual about Temporary Objects comes close. It seems to say this might be implementation dependent. It would be most useful if I could count on the virtual nature of a returned object to be alive as long as I don't actually assign it to something of the base type. Consider the following real application. First I will describe it, and then provide a shar archive of source code suitable for experimenting on a Sun. ( Ok for Zortech too but you must remove the complex type or add a small complex implementation for Zortech ) EXAMPLE: Extend the Abstract Syntax Tree interpreter example from the Dewhurst and Stark textbook page 110 to handle types other than plain vanilla integers. In a Binop node you will have an expression like: return left->eval() + right->eval(); In order to avoid garbage collection and frequent inefficient invocations of new, it is desirable to return an actual Data object. Thus the eval() function returns 'Data' and things like Int, Real, and Cpx are derived from Data. Multiple inheritance is used since Int, Real, etc derive from both Node and Data. The eval() function actually returns, for example an Int. Then the '+' operator becomes the overloaded virtual '+' that is a member function of the Int class. If virtual-ness is lost when the function returns the Int, then the default Data class '+' gets invoked. This is what happens in Zortech 2.06. So, who is right? What can I count on? Charlie Havener - charlie@genrad.com - 508-369-4400 x3302 Shar archive below --------------------- echo x - Nodes.C cat >Nodes.C <<'!Funky!Stuff!' // Nodes.C - Implementation of Nodes.h for AST #include #include Data Node::eval() { cerr << "In Node virtual eval()"; return Int(0);} Data Data::operator+(const Data&) { cerr << "virtual Data::operator+()!!!\n"; return Int(0); } Data Int::operator+(const Data& y) { switch ( y.GetType() ) // GetType is virtual, so not really inline { case INT: return Int(u.ival + y.u.ival); case REAL: return Real(u.ival + y.u.rval); case UNKNOWN: case COMPLEX: default: cerr << "unknown rhs type in int'+' operator \n"; return Int(0); } } Data Real::operator+(const Data& y) { switch ( y.GetType()) { case INT: return Real(u.rval + y.u.ival); case REAL: return Real(u.rval + y.u.rval); case UNKNOWN: case COMPLEX: default: cerr << "unknown rhs type in real '+' operator \n"; return Int(0); } } Data Cpx::operator+(const Data& y) { switch ( y.GetType()) { case INT: return Cpx(u.cpxval + y.u.ival); case REAL: return Cpx(u.cpxval + y.u.rval); case COMPLEX: return Cpx(u.cpxval + y.u.cpxval); case UNKNOWN: default: cerr << "unknown rhs type in real '+' operator \n"; return Int(0); } } !Funky!Stuff! echo x - tiny.C cat >tiny.C <<'!Funky!Stuff!' // This example is a part of an Abstract Syntax Tree // as described in Dewhurst and Stark book // extended to handle Complex Numbers,& Reals #include #include #include main() { Node *np; cout << "Tiny expression interpreter running\n"; np = new Int(7); (np->eval()).print(); delete np; cout << "\n"; np = new Plus(new Real(5.42), new Int(67)); (np->eval()).print(); delete np; cout << "\n"; np = new Plus(new Cpx(5), new Cpx(93)); (np->eval()).print(); delete np; cout << "\n"; np = new Plus(new Cpx(5), new Real(6.4)); (np->eval()).print(); delete np; cout << "\n"; } !Funky!Stuff! echo x - Nodes.h cat >Nodes.h <<'!Funky!Stuff!' /* Nodes for Abstract Syntax Tree */ #ifndef NODES_H #define NODES_H #include #include //---------------- Data Related classes follow ----- class Value { public: union // anonymous union! { int ival; double rval; }; complex cpxval; // can't have class object with ctor in a union }; class Data // an impure abstract class { // could put type info here but that makes the Data object bigger protected: Data() {} // forbid direct instantiation Value u; // derived classes can access it only if friends friend class Int; friend class Real; friend class Cpx; public: enum DataType { INT, REAL, COMPLEX, UNKNOWN }; // cannot make pure virtual funcs below in C++ 2.0 // because no func can return a pure abstract class like Data virtual DataType GetType() const { return UNKNOWN; } virtual ~Data() {} virtual Data operator+(const Data&); virtual void print(void) { cout << "oops! virtual data base class " ; } virtual Data eval() { cerr << "Data eval() base class\n"; return *this;} }; // ------------- Parse tree nodes ---------------- class Node { protected: // Node(){} public: Node(){} virtual ~Node() {} virtual Data eval(); virtual void print() { cerr << "No print() defined\n"; } }; class Binop : public Node { protected: Node *left; Node *right; Binop(Node *l,Node *r) { left = l; right = r;} ~Binop() { delete left; delete right; } }; class Plus : public Binop { public: Plus(Node *l,Node *r) : (l,r) {}; Data eval() { return ( left->eval() + right->eval()); } }; //---------------- Data Related classes follow ----- // class Int : public Node, public Data // this order fails class Int : public Data, public Node { public: Int(int t = 0) { u.ival = t; } // no dtor needed since ctor didn't use new DataType GetType() const { return INT; } Data eval() { return *this; } void print(void) { cout << u.ival ; } Data operator+(const Data&); }; class Real : public Data, public Node { public: Real(double t) { u.rval = t; } DataType GetType() const { return REAL; } void print(void) { cout << u.rval ; } Data eval() { return *this; } Data operator+(const Data&); }; class Cpx : public Data, public Node { public: Cpx(complex t) { u.cpxval = t; } DataType GetType() const { return COMPLEX; } void print(void) { cout << u.cpxval ; } Data eval() { return *this; } Data operator+(const Data&); }; #endif !Funky!Stuff! echo x - makefile cat >makefile <<'!Funky!Stuff!' #makefile for the object oriented design expression #interpreter. Uses the Sun C++ 2.0 #Charlie Havener SHELL= /bin/sh CC= /usr/local/CC/sun3/CC FLAGS= tiny: tiny.o Nodes.o $(CC) $(FLAGS) -otiny tiny.o Nodes.o -lcomplex Nodes.o: Nodes.C Nodes.h $(CC) -c -I. $(FLAGS) Nodes.C tiny.o: tiny.C Nodes.h $(CC) -c -I. $(FLAGS) tiny.C listing: Nodes.C tiny.C Nodes.h makefile pr -f makefile Nodes.h tiny.C Nodes.C > listing archive: Nodes.C tiny.C Nodes.h makefile rm archive shar archive Nodes.C tiny.C Nodes.h makefile clean: rm Nodes.o tiny.o tiny !Funky!Stuff!