Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Path: utzoo!mnetor!seismo!munnari!otc!mikem From: mikem@otc.OZ (Mike Mowbray) Newsgroups: comp.lang.c++ Subject: Re: Questions about C++ (Rather Long) Message-ID: <131@otc.OZ> Date: Sun, 21-Jun-87 06:10:18 EDT Article-I.D.: otc.131 Posted: Sun Jun 21 06:10:18 1987 Date-Received: Mon, 22-Jun-87 06:27:58 EDT References: <226@nih-csl.UUCP> Distribution: comp Organization: O.T.C. Systems Development, Australia Lines: 302 In article <226@nih-csl.UUCP>, keith@nih-csl.UUCP (keith gorlen) says: > Suppose you're implementing a Smalltalk-like class Dictionary. Class > Dictionary is a derived class of Collection, which in turn is derived > from class Object: > > class Object { > ... > }; > > class Collection : public Object { > ... > virtual void add(const Object&); // add an Object to a Collection > ... > }; > > class Dictionary : public Collection { > ... > virtual void add(const Object&); // add an Association to a Dictionary > ... > }; > > The problem is that class Dictionary should only accept instances of > class Association (which is a key object - value object pair) or of a > class derived from Association as arguments, so you should check the > class of the argument. You can't do this just by defining > Dictionary::add(const Association&), because you can't overload a > virtual function in a derived class without also overloading it in the > base class. This means that to use static type checking you'd have to > also define Collection::add(const Association&), which is undesirable > because you should be able to define new kinds of Collections without > having to modify class Collection itself. So to make Dictionary::add > check its argument class you need to do it at run-time with isKindOf > (a.k.a. "in"). Yes, this is annoying and I have indeed run into it myself. However, I not sure whether such designs are really a good idea or not. (I know lots of people do it, but that does not necessarily mean it's good style.) My reservations are based on the fact that we are essentially asking class Collection to do something beyond what normal Collections do. I.e: we are passing an Object in and relying on the actual virtual function to return an error or something if the Object was not an Association. This might be okay, and it might not. I think it's too early to really know. More real-world usage of object-oriented techniques in critical high-performance applications with large maintenance problems is required. By the way, it IS possible to achieve a limited "isa" mechanism in the current version of C++, for certain cases. It relies on knowledge of what cfront generates, and therefore should be put into the same category as the "object is first (hidden) argument" stuff. It can only be used to check the type of things with virtual functions. (One objection to adding this feature to C++ proper is the run-time overhead. Where virtual functions already exist, there is already a pointer which can in principle be used if you're desperate enough.) The following program illustrates the idea. Use this at your own risk though. // *********************** typetst.c ************************************* #include #define IS_TYPE(classtype,instance) ((instance)._vptr == ::classtype\ __vtbl) // "classtype" must have virtual functions. // "instance" must be an instance of a class with virtual // functions (or a reference thereto). // If either is not the case, there'll be a compile-time error. // (either "_vptr: no such member" // or "xxxxx__vtbl is undefined" // or something like that.) //-------------------------------------------------------- class Object { virtual void func() { } }; class Association : public Object { // ...... }; class Collection : public Object { // ..... public: virtual int add(Object&) // return 0 if error. { return 0; } }; class Dictionary : public Collection { // .... public: int add(Object &obj) // return 0 if arg not an Association { if (IS_TYPE(Association,obj)) { // ... do whatever ... return 1; } else return 0; } }; //-------------------------------------------------------- main() { Object obj; Association assoc; Dictionary dict; Collection *col = &dict; cout << col->add(obj) << " " << col->add(assoc) << "\n"; // should output: 0 1 } // **** (end) ************************************************************ And it does indeed work, at least on my present version of cfront (1.2.1): $ typetst 0 1 $ Note also, that IS_TYPE doesn't work for derived cases: Association assoc; if (IS_TYPE(Object,assoc)) // will yield 0. ..... To remedy this is harder since extra type information is needed. E.g: you could have a table associated with each class which keeps track of all its ancestors' vptrs. Each instance would need a pointer to the correct table. The following is an example: // ************* typetst.c *********************************************** #include typedef int (**Type)(...); //---------------------------------------------------------------------- class AncTypes { // Ancestor Types Type *tbl; // array, terminated by a NULL int sz; int sealed() { return sz < 0; } void do_enrol(Type t); public: void enrol(Type t, int s) { if (!sealed()) { do_enrol(t); if (s) sz = -sz; } } int has(Type); }; void AncTypes::do_enrol(Type tp) { if (sz == 0) { tbl = new Type [sz=1]; *tbl = tp; } else { // see if already there register Type *ceiling = tbl+sz; for (register Type *t = tbl; t < ceiling; ++t) if (*t == tp) return; // not there, so enrol... Type *newtbl = new Type [++sz]; Type *tmp = newtbl; for (t = tbl; t < ceiling; ) *tmp++ = *t++; *tmp = tp; delete tbl; tbl = newtbl; } } int AncTypes::has(Type tp) { for (register Type *t = tbl + (sz<0 ? -sz : sz) - 1; t >= tbl; --t) if (*t == tp) return 1; return 0; } //---------------------------------------------------------------------- class Object { static AncTypes _atypes; AncTypes *_atptr; public: void init(AncTypes *at, AncTypes &ps) { if (at==NULL) (at=&ps)->enrol((Type)_vptr,1); else at->enrol((Type)_vptr,0); _atptr = at; } Object(AncTypes *at=NULL) { init(at,_atypes); } virtual void dummy() { } AncTypes &atypes() { return *_atptr; } }; #define IS_KIND(classtype,instance) ((instance).Object::atypes().has((Type)::classtype\ __vtbl)) //---------------------------------------------------------------------- // Now some classes derived from Object. Note that the additional // complexity in the classes and constructors is quite minimal. class Association : public Object { static AncTypes _atypes; void dummy() { } // ...... public: Association(AncTypes *at=NULL) : (at ? at : &_atypes) { init(at,_atypes); } }; class Special_Assoc : public Association { static AncTypes _atypes; // ...... public: Special_Assoc(AncTypes *at=NULL) : (at ? at : &_atypes) { init(at,_atypes); } }; class Collection : public Object { static AncTypes _atypes; // ..... public: Collection(AncTypes *at=NULL) : (at ? at : &_atypes) { init(at,_atypes); } virtual int add(Object&) // return 0 if error. { return 0; } }; class Dictionary : public Collection { static AncTypes _atypes; // .... public: Dictionary(AncTypes *at=NULL) : (at ? at : &_atypes) { init(at,_atypes); } int add(Object &obj) // return 0 if arg not an Association { if (IS_KIND(Association,obj)) { // ... do whatever .... return 1; } else return 0; } }; //---------------------------------------------------------------------- main() { Association assoc; Dictionary dict; Collection *col = &dict; Special_Assoc spec1, spec2; Object obj; cout << col->add(obj) << "\n"; // 0 cout << col->add(assoc) << "\n"; // 1 cout << col->add(spec1) << "\n"; // 1 cout << col->add(spec2) << "\n"; // 1 cout << col->add(dict) << "\n"; // 0 } // **** (end) ************************************************************ The way it works is that derived objects pass a non-null argument to their Base constructor and this ultimately modifies the way the Object constructor operates. When one instance of any class has been constructed, a locking mechanism comes into play, which reduces the amount of overhead associated with creating further instances of that particular type of object. The mechanism above could obviously be made a bit faster by using something more sophisticated than linear search. Obviously, a compiler could make this nicer, but there's always going to be lots of run-time overhead involved, so maybe it's better left up to the user. Needless to say, use of this sort of thing is for those with open eyes. There's a number of holes one could fall down since there's no error checking. I daresay that it should always be possible to do this sort of thing, unless cfront is modified to explicitly deny it (and even then one could play silly-buggers with ordinary C functions to achieve a similar result, I think). As I said above though, I'm ambivalent about whether this design style is, in principle, good or bad. Mike Mowbray Systems Development Overseas Telecommunications Commission (Australia) UUCP: {seismo,mcvax}!otc.oz!mikem ACSnet: mikem@otc.oz