mikem@otc.OZ (Mike Mowbray) (06/09/87)
Since the article by dcw@doc.ic.ac.uk (Duncan C White) is likely to make others unnecessarily wary of C++, I decided to reply here, rather than by direct e-mail. In article <452@ivax.doc.ic.ac.uk>, dcw@doc.ic.ac.uk (Duncan C White) says: > > Question 1: There seems to be an ambiguity in the Book which could cause > ---------- a lot of confusion if not cleared up: > > For example, given the following [an extract from 'class hierarchies' S:7.5.2] > > class employee > class manager : employee > class director : manager > class vice_president : manager > class president : vice_president > > there are several SUBCLASSES of employee: > > manager, director, vice_president and president > > whereas manager is the ONLY IMMEDIATE SUBCLASS of employee. > [In general, however, there may be several IMMEDIATE SUBCLASSES: > director AND vice_president are BOTH immediate subclasses of manager] > > Which of these terms corresponds to 'derived class' ? In C++, manager, director, vice_president and president would all be said to be "derived from employee". "Derivation" is a generic term indicating that properties of one class are inherited from another, whether or not the ancestor is immediate. > Similarly, there is ONLY one IMMEDIATE SUPERCLASS of director [manager] : > does this correspond to the base class definition ? Or is employee a base > class of director, as well ? Yes (the latter). > A Basic Difference : > ------------------ > [ ... deleted ... ] > > We had assumed that the basic facilities SIMULA and C++ provide would be the > same : after all, the Introduction says that C++ was designed [ via C with > classes ] as an alternative to SIMULA, but less expensive at run-time. > > However, SIMULA allows the following: > >> class vehicle ... // class defns >> class car : vehicle ... >> class fourwheeler : car ... >> >> ref( vehicle ) v; > > NOTE : in SIMULA, all class variables are declared and initialised separately > so in this example, 'v' is declared as a reference to a vehicle, [reference > appears to be the same term as in C++]. v has not been initialised yet. > The C++ style declaration 'vehicle v' is meaningless in SIMULA. > > Clearly, then, v could be initialised [ SIMULA actually uses ':-' as a special > assignment operator for refs ] by the following: > >> v = new vehicle; > > However, SIMULA allows an important 'other' style of initialization : > >> v = new car; > > which initialises v, but not to a vehicle-instance: to a car-instance [ car > must be a subclass of vehicle ] > > C++ objects [vehemently] to this kind of use : probably because it has many > implications for run-time behaviour. No. C++ is excellent for doing this sort of thing, but requires a bit of practice. E.g: #include <stream.h> class Vehicle { public: virtual int wheels() { return 0; } // default }; // Every vehicle is assumed to have wheels, hence the wheels() member // function is virtual. class Car : public Vehicle { public: int wheels() { return 4; } // Cars have four wheels }; class ThreeWheeler : public Car { public: int wheels() { return 3; } // 3-wheeler cars have // three wheels. }; // The following function has no knowledge of exactly what kind of Vehicle // is being passed to it. It only knows that given a Vehicle, you can // find how many wheels it has by calling the member function wheels() void showwheels(Vehicle &v, char *s) { cout << s << " has " << v.wheels() << " wheels\n"; } // Create some instances, and print them out: main() { Vehicle v; // a real Vehicle Vehicle *vc = new Car; // pointer to Vehicle that // is made to point to an // instance of Car. (The "new" // operator returns a pointer // to an instance of the class // type.) Vehicle &vcr = *(new Car); // reference to Vehicle that // is initialised to refer to // an instance of Car. Note // that de-referencing of the // pointer returned by "new" // is necessary. Vehicle *vtw = new ThreeWheeler; // pointer to Vehicle, made // to point to an instance of // ThreeWheeler. Car c; // A real Car. // Now show how many wheels are in each: showwheels(v, "v"); showwheels(*vc, "vc"); showwheels(vcr, "vcr"); showwheels(*vtw, "vtw"); showwheels(c, "c"); } When this is run, you get the following output: v has 0 wheels vc has 4 wheels vtw has 3 wheels c has 4 wheels QED. > This basic problem comes to light again and again : Yes, it is a common need. C++ addresses it very well. > o You can't declare a function which takes a parameter of type vehicle, > and then call it with an expression which is a car-instance. Yes you can. The thing is that the base classes have to be public. In the above example, if we had written: class Car : Vehicle { // .... }; it wouldn't have worked, because this declaration says that Car inherits all the properties of Vehicle, but doesn't show the public properties of Vehicle to the outside world, hence a Car could not, in this case, be used where a reference to Vehicle was expected. Changing it to class Car : public Vehicle { // ... }; fixes this. > o You can't define a 'list of vehicles' into which you can put any > subclass-instances [ cars, planes, boats, pogo-sticks, minis, fords, > aston martins etc.. ] Yes, you can. The only criterion is that the subclass-instances must have vehicle as a public base class. I have written general linked list classes, general hashtable classes, general binary tree classes, etc, by making use of this facility. > o Associated operators like 'in' and 'qua' [ see below ] aren't available These are not really needed. You achieve the same result in other ways. See below. > It seems to us, that the omission of such facilities in C++ is a major > obstacle to 'object-orientated' programming : the usual SIMULA approach no > longer works. Why the hell can't a list of vehicles contain some cars, some > planes, some horse-drawn carriages, and some pogo-sticks ?? Fortunately, these facilities are NOT omitted from C++. Indeed, it is these features that make C++ an excellent language. > Question 2: Is this omission as serious as it appears ? Can the same > ---------- facilities be provided another way, or is there a separate > "C++ programming metaphor" which entails a different way > of analysing problems so that the missing features are never > needed ?? Since it is not an omission, this question need not now be answered. > NOTE: The Book seems to suggest two related techniques : > > generic vectors [ S:1.16 ] > > which suggests writing macros to 'build' class definitions for specific > subclass lists [ eg, declare( vector, int ) ] Personally I don't really like this method, as macros are harder to work with. Nevertheless, the technique described is quite okay. > However, this builds an 'integer-vector' class definition, rather than a > 'vector of things which are any subclasses of integer' You can't have things which are derived from built-in types. It is questionable whether this should be the case. However, I've never felt an urgent need for it. > polymorphic vectors [ S:17 ] > > However, this technique seems to rely on 'void *' to contain a pointer to > the elements: surely this completely defeats the type-checking scheme ? Yes. However, there's nothing to stop the programmer writing a generic vector which had pointers to a suitable base class. Then the type-checking would be improved, though still not complete. This is done along the lines given in the book. > Explanation of 'in' and 'qua' : > ----------------------------- > > 1). The boolean SIMULA function : > > i in c > > returns true if the instance, i, is an instance of the given class, c, > or if i is an instance of any subclass of c [ false otherwise ] > > For example: > > a 'car' instance is in 'car' > a 'car' instance is ALSO in 'vehicle' > a 'fourwheeler' instance is in 'fourwheeler', 'car' and 'vehicle' Interesting. I've never felt the need to use such a feature. By structuring the inheritance hierarchies properly, this can be avoided, I think. > 2). The polymorphic SIMULA function : > > i qua c > > "qualifies" the instance, i, as a class, c. [ more explanation below example ] > > For example: given the following: > >> ref( vehicle ) v; >> v = new car; // vehicle v is a car: C++ would already object > > The following use, despite actually being logically correct, fails to compile : > >> print v.wheels; // attempt to print the number of wheels >> // that the car-instance v has. > > whereas this is ok: > >> print (v qua car).wheels; If we had: class Car : Vehicle { public: int wheels() { return 4; } int refuel(); }; then the following could be done: Vehicle *v = (Vehicle*) new Car; ((Car*)v)->refuel(); Note that explicit casting is necessary to initialise v, since here Car does not have Vehicle as a public base class. Similarly, to access Car::fuel, a further explicit cast is required. Needless to say, such constructions are an indication of poor structure, and should be avoided as much as possible. > Note: the semantics of qua are a strange mixture of compile-time and run-time > events. At compile-time, v must be an instance of a car, or an instance of a > superclass of car, otherwise a compile time error is generated. > However, at run-time, v is checked to see that it actually is 'in' car, and > a fatal run-time error is generated if not. C++ does not use any features that require storing information of what types a thing is derived from. This is for reasons of efficiency, and to adhere to the basic philosophy that if something doesn't use a feature, it should not have to pay for it. > Finding the Immediate Superclass: > -------------------------------- > [ ... deleted ... ] > > Question 3 : Can anyone think of a nicer way to implement in ? > ---------- We tried : I would try to re-structure my design so that it was not necessary. Such things are very inefficient. > >>typedef int boolean ; >> >>class universal_class { >> static char *name = "universal_class"; > > This, of course, caused us to run straight into the > > 'initialization of static class members' > > problem discussed a few articles ago.. ( of course, we didn't read that > article until we had wasted several hours trying various increasingly > desperate and mucky methods ... :-) If you really, really, want to do this, the following is a possibility: class Universal_Class { static char *name; public: Universal_Class(); char const *nm() { return name; } }; Universal_Class::Universal_Class() // constructor { if (name == NULL) name = "Universal_Class"; } This relies on the fact that static things start off 0. The "nm" member is there so that anything can read Universal_Class::name safely. > Conclusion: > ---------- > > There are many aspects in C++ which we like [ operator overloading and true > reference variables especially ] but it does seem to be, in some sense, a > "weaker kind" of class-based language than SIMULA. I think C++ classes are VERY powerful, but like any new thing they do require the programmer to both read the book carefully, and get some experience. This is not to say the C++ is the final answer. For example, adding features such as multiple inheritance, parametrized classes, exception handling, etc, would be good. However, C++ is designed for high efficiency and these features take time to develop under such circumstances. I find it interesting how so much has already been added to C, which is considered quite a stable language, without introducing serious backward incompatibility. The ongoing enhancement of C++ seems to prove that it IS possible for a language to be both reliable and stable, but also evolving. Mike Mowbray Systems Development Overseas Telecommunications Commission (Australia) UUCP: {seismo,mcvax}!otc.oz!mikem ACSnet: mikem@otc.oz
west@calgary.UUCP (Darrin West) (06/10/87)
In article <125@otc.OZ>, mikem@otc.OZ (Mike Mowbray) writes: > Since the article by dcw@doc.ic.ac.uk (Duncan C White) is likely to make > others unnecessarily wary of C++, I decided to reply here, rather than by > direct e-mail. > > In article <452@ivax.doc.ic.ac.uk>, dcw@doc.ic.ac.uk (Duncan C White) says: > > [stuff - omitted] which was replied to, admirably, and politely. > > > o Associated operators like 'in' and 'qua' [ see below ] aren't available > > These are not really needed. You achieve the same result in other ways. See > below. I don't think you can make this blanket statement. There are a lot of simulationists who would not like to live without it. :-) ? > > > Explanation of 'in' and 'qua' : > > ----------------------------- > > > > 1). The boolean SIMULA function : > > > > i in c > > > > returns true if the instance, i, is an instance of the given class, c, > > or if i is an instance of any subclass of c [ false otherwise ] > > > > For example: > > > > a 'car' instance is in 'car' > > a 'car' instance is ALSO in 'vehicle' > > a 'fourwheeler' instance is in 'fourwheeler', 'car' and 'vehicle' > > Interesting. I've never felt the need to use such a feature. By structuring > the inheritance hierarchies properly, this can be avoided, I think. This is probably the best C++ solution to the problem: use what is available! Otherwise you might end up in a situation like "running a C program on an interpreter written in lisp". Each language has its forte's (how DO you make an egrave [or whatever]), but forcing either one to do what the other is good at may lead to problems. > > > 2). The polymorphic SIMULA function : > > > > i qua c > > > > "qualifies" the instance, i, as a class, c. > > > > For example: given the following: > > > >> ref( vehicle ) v; > >> v = new car; //vehicle v is a car: C++ would already object > > > > The following use, fails to compile : > > > >> print v.wheels; // attempt to print the number of wheels > >> // that the car-instance v has. > > > > whereas this is ok: > > > >> print (v qua car).wheels; > > If we had: > class Car : Vehicle { > public: > int wheels() { return 4; } > int refuel(); > }; > > then the following could be done: > > Vehicle *v = (Vehicle*) new Car; > > ((Car*)v)->refuel(); > > Note that explicit casting is necessary to initialise v, since here Car does > not have Vehicle as a public base class. Similarly, to access Car::fuel, a > further explicit cast is required. Needless to say, such constructions are > an indication of poor structure, and should be avoided as much as possible. > [Reformatted without permission:] > > Note: the semantics of qua are a strange mixture of compile-time and > > run-time events. At compile-time, v must be an instance of a car, or an > > instance of a superclass of car, otherwise a compile time error is > > generated. However, at run-time, v is checked to see that it actually > > is 'in' car, and a fatal run-time error is generated if not. This is the crux of the issue. And can be easily dealt with, if you want to spent valuable run time doing it. I rewrote class simset of simula in C++, and ran across the famous check to see if the next linkage element was a link, or a head (ie have we reached the end of the circular list? The following was the solution I came up with. I can't say that I totally understood the simula code I was copying, but attempted to do a "one to one" conversion as best I could. The point of interest is the "in_link" virtual function. It is redifined to return 1 only in link. This allows a run time check of any linkage object to determine its type (sort of). A different way to do this (and I suspect that this is what simula actually does) would be to have a member variable of linkage (say "int type") be set to a unique value in each different derivation of linkage. So link would set "type" equal to one, and head would set "type" equal to 2 in their constructors. This would be slightly faster than dereferencing the virtual function pointers, but does cost at run time (and development time). My personal preference is to find a different way to do the same thing. Since C++ does not provide this facility automatically (qua), why not use your imagination, and come up with a viable alternative. For example have a head object which points to the head and tail, and don't use a circular list (ie mark the end of the list with NULL in the same way that simula marks the end of the list with a head object). simset.h #ifndef NULL #define NULL 0 #endif class link; class head; class linkage { public: linkage *SUC,*PRED; virtual int in_link() {return(0);} /* a better way to do this might be with a flag variable which is set to true only in a "link"'s constructor */ link* suc(); link* pred(); }; class link : public linkage { public: int in_link() {return(1);} void out(); void follow(linkage *); void precede(linkage *); void into(head *); }; class head : public linkage{ public: link* first(); link* last(); int empty() {return(SUC == this);} int cardinal(); void clear(); head(); /* Constructor, run upon creation of new "head" */ }; simset.h #include "simset.h" link* linkage.suc() { if (SUC->in_link()) return((link*)SUC); else return(NULL); /* pointing back at head */ } link* linkage.pred() { if (PRED->in_link()) return((link*)PRED); else return(NULL); /* pointing back at head */ } void link.out() { if (SUC != NULL){ SUC->PRED = PRED; PRED->SUC = SUC; /* can PRED be NULL? */ SUC = PRED = NULL; } } void link.follow(linkage *X) { out(); /* remove from other sets */ if (X != NULL){ if (X->SUC != NULL) { /* can be combined with an && */ PRED = X; SUC = X->SUC; SUC->PRED = X->SUC = this; } } } void link.precede(linkage *X) { out(); if (X != NULL){ if (X->SUC != NULL){ /* can be combined with an && */ SUC = X; PRED = X->PRED; PRED->SUC = X->PRED = this; } } } void link.into(head *S) { precede(S); } head.head() { /* run upon construction of an object of type head */ SUC = PRED = this; } link* head.first() { return(suc()); } link* head.last() { return(pred()); } int head.cardinal() { int i = 0; linkage *x; x = this->suc(); while (x != NULL){ i++; x = x->suc(); } return(i); } void head.clear() { link *x; x = first(); while(x != NULL) { x->out(); x = first(); } } and a test program. #include <stdio.h> #include "simset.h" class person : public link{ public: virtual void print() { printf("person\n");} }; class male : public person{ public: virtual void print() { printf("male\n");} }; class female : public person{ public: virtual void print() { printf("female\n");} }; main() { int i; head *h; person *t; h = new head; for (i = 0; i< 10; i++) { (new male)->into(h); (new female)->into(h); (new person)->into(h); } printf("card: %d\n",h->cardinal()); while (!h->empty()){ t = (person*)h->first(); t->print(); t->out(); } } > > C++ does not use any features that require storing information of what types > a thing is derived from. This is for reasons of efficiency, and to adhere to > the basic philosophy that if something doesn't use a feature, it should not > have to pay for it. > > > Finding the Immediate Superclass: > > -------------------------------- > > [ ... deleted ... ] > > > > Question 3 : Can anyone think of a nicer way to implement in ? > > ---------- We tried : > > I would try to re-structure my design so that it was not necessary. > Such things are very inefficient. > > > > >>typedef int boolean ; > >> > >>class universal_class { > >> static char *name = "universal_class"; Were you honestly considering using strcmp to tell you which class you had found? You might want to make "name" or "Name" a virtual function that is redifined upon further derivation. C++ gives me more programming power combined with efficiecy at run time than any other language I have run across. I am using it to write my thesis project, which is a Time Warp emulator. (ie a study of an optimistic synchronisation mechanism which is very useful for distributing a simulation. See "Virtual Time" - David Jefferson ACM '85?) It (C++) has turned out to be quite handy! -- Darrin West, Master's Unit (read: student). ..![ubc-vision,ihnp4]! Department of Computer Science alberta!calgary!west University of Calgary. Can you say '88 Winter Games? Brain fault (cortex dumped)
dcw@icdoc.UUCP (06/12/87)
Thanks for the many messages we received about our questions about C++ versus SIMULA. This article, and the following two, are a summary of what we have learned. I'm afraid the third part [ in, super, qua and conclusion ] is again very large... NB: There may be some overlap with Mike Mowbray's response <125@otc.OZ> - I did ask people not to respond via news... ---------------------------------------------------------------------------- Part1/3 Derived and base class defns We asked whether 'derived class' ---------------------------- corresponded to 'ancestor' or only to 'immediate ancestor' : parent. Paul Calder <calder@uluru.stanford.edu> explained : >"Derived class" and "base class" are like your "SUBCLASS" and "SUPERCLASS", >except that some rules apply only to "IMMEDIATE" relatives. For example - > > "A pointer to a class may be converted to a pointer to a public > base class (read "any of the SUPERCLASSES") of that class" > Section 6.7 > > "... the public members of the base class (read "IMMEDIATE SUPERCLASS") > are public for the derived class ..." > Section 8.5.9, para 2 > >In general, The Book seems to be fairly clear, using expressions like "a >base class" where any will do, and "the base class" where only the immediate >parent is meant. I find that the semantics are usually intuitive. This shows that there is a certain amount of ambiguity, but it seems to be a fair summary, so let us define : o a base class == superclass o THE base class == immediate superclass o a derived class == subclass o THE derived class == immediate subclass Andrew Koenig, AT&T Bell Labs <ark@alice.uucp> and David Wonnacott, AT&T Corporate Education <davew@whuxm.uucp> came to the same conclusion.
dcw@icdoc.UUCP (06/12/87)
Summary, part 2/3: Our 'Basic Difference' We thought that SIMULA allows a subclass ---------------------- instance to be assigned to a superclass variable, and that C++ doesn't. Many people pointed out that THIS IS DEAD WRONG : [ ooooppps... ] In SIMULA, the ONLY way of declaring an instance is: ref( fred ) x; // x is an instance of fred, which is a // class declared somewhere above Our mistake was that we assumed that C++, like SIMULA, had ONLY one way to define instances : fred x; // instance of fred whereas, of course, an instance can also be declared using : fred *xptr; // not initialised yet: like SIMULA ref or: fred& xref = x; // must have some initialization with it, // except when its a function parameter So, C++ is quite happy with : vehicle *v = new car; and car c; vehicle &v = c; This, of course, means that you can, after all: >...declare a function which takes a parameter of type vehicle *, >and then call it with an expression which is a car *. > >...define a 'list of vehicles' into which you can put any >subclass-instances [ cars, planes, boats, pogo-sticks, minis, fords, >aston martins etc.. ] Several people commented that C++ would indeed be a weak form of object orientated language if it couldn't handle this kind of thing: this is precisely what we thought [ and had trouble believing ]. NB: Another common problem was mentioned by a few people: if any member of the class hierarchy was not derived publicly from its immediate superclass, even the pointer conversions would not work... For example: class vehicle .. class car : vehicle .. // if 'public' omitted, then vehicle *v = new car; // this declaration is rejected, apparently
dcw@icdoc.UUCP (06/12/87)
*** WARNING: 300 LINE ARTICLE... HIT j IF YOU'RE NOT INTERESTED... Summary, part 3/3: 'IN', 'QUA' and 'SUPER': We found that several important ----------------------- operators aren't available in C++. a). SUPER Paul Calder <calder@uluru.stanford.edu> said : >It is mildly inconvenient that C++ does not provide COMPILE-TIME access to >a derived class's base class. We have found that there are a number of >situations where a virtual function calls the corresponding function in its >base class .... It would be convenient to specify this as base:: I'm not sure about 'compile-time only'.. but even that would be a help. Jacob Gore <gore@nucsrl.uucp> Northwestern University, Computer Science Research Lab, [ Chicago? ] sent us a fascinating letter about Objective-C. He says: >From my experience, 'super' is very useful. Most often, it is used to envoke >the 'default action', as specified by the superclass, after performing the >action specific to this class. A method 'doSomething' in class C may perform >some action, and then call the method 'doSomething' in its superclass. Of >course, if the superclass' name is known, it can be used in place of 'super', >but (a) the specific name is not always known, (b) using the specific name >hinders reusability, and (c) using 'super' all the time is more convenient, as >well as more obvious. Jonathan Shopiro, AT&T Bell Labs, however, suggested that super was unnecessary, saying: >... just as you almost never have >to determine the class of an object in Smalltalk, you never need it in >C++ either. To summarise, most people felt the need for some form of 'super', although this was not unamimous. b). QUA Thomas Maslen <maslen@rocky.stanford.edu> suggested: >Type-casting can be used for the "treat it as an X" part of qua. Note, however, that SIMULA would give a compile-time error if the instance belongs to an entirely different hierarchy, whereas C++ will let it straight through. David Wonnacott <davew@whuxm.uucp> also suggested type-casting, giving the example: >vehicle *vp = new car; > > ((car *) vp) -> wheels(); > And continued with: >Note that, when you put in an explicit type cast, NO type checking is done. >It is therefore up to the programmer to remember that vp does, in fact, point >to a car.. If vp pointed to a boat (no function called wheels()), strange >things (not an error) would happen at run time. This is an important point: in both languages, it is up to the programmer to ensure that a vehicle-instance is actually a car before applying QUA [or type casts]. However, at least SIMULA will give an immediate run-time error, whereas C++ will carry on regardless... c). IN Bjarne Stroustrup suggested : >A piece of advice: If you can avoid it, don't even simulate IN. Most >often you don't need it. I consider INSPECT and QUA efficiency hacks >in Simula. In C++ you can most often dispense with them in favor of >using virtual functions. >If you must, and in general, you access a member of a base class >(immediate or not) by prefixing the member name by its class name: David Wonnacott <davew@whuxm.uucp> says, of the IN operator: >C++ does not provide any facility for this, as it would involve a >storage overhead. As I understand it, the only place where C++ adds any >storage or run-time calculation is with virtual functions. > >There are, however, many times when you would like to find out what class >an object is, or determine whether it is derived from a given class. This >can be accomplished by creating a base class (traditionally called "object"), >and deriving all other classes from it. class object an then have member >functions equivalent to "in". Writing such a class is pretty hard, and I >suggest that you look around for a public domain C++ library that provides >such facilities (I know one exists, you might try asking on the net). I'm very interested in such a PD C++ library... any comment, anyone ?? Several people suggested ways of implementing a restricted, 'IMMEDIATE IN' which we will call 'isa' : Andrew Koenig <ark@alice.uucp> made an excellent suggestion: >So far, we have had to rely on our own devices to determine what >kind of individual is designated by an employee pointer. We would >really like the implementation to keep track of that for us. For >example, we might like to have a string called, say "title" that >represents the rank of the employee. > > class employee: { > virtual char* title() { return "employee"; } > // other stuff > }; > > class manager: employee { > char* title() { return "manager"; } > // other stuff > }; > >... ditto for director > >... Now, if we have an employee pointer ep, we can say: > > printf ("%s\n", ep->title()); > >and it will print the the actual employee title. Andrew's scheme works very nicely: add to employee the following function: [not virtual, not overloaded: ONLY in employee] boolean isa( char *p ) { return strcmp( this->title(), p ) == 0; } and then all subclasses of employee can use it. Some examples: void test( char *s, boolean b ) { cout << s << ": " << ( b?"true\n":"false\n" ) ; } employee e; employee *eptr = new manager; manager *mptr = new manager; test( "e isa employee" , e.isa("employee") ); test( "e isa manager" , e.isa("manager") ); test( "eptr isa employee", eptr->isa("employee") ); test( "eptr isa manager:", eptr->isa("manager") ); test( "mptr isa employee", mptr->isa("employee") ); test( "mptr isa manager" , mptr->isa("manager") ); .... and many other similar tests one can think of.. Paul Calder <calder@uluru.stanford.edu> suggested a similar scheme. Jacob Gore <gore@nucsrl.uucp> says that Objective-C provides: > [x isMemberOf: C] iff x is an instance of class C > [x isKindOf: C] iff x is an instance of C' and C' is a subclass of C > (also, if [x isMemberOf: C] then [x isKindOf: C]). From this description, isMemberOf is 'isa' and isKindOf is 'in'. It sounds like Objective-C has more run-time support than C++ [comments welcome] It sounds like a nice language: where can we get it, anyone ? CONCLUSION: ---------- I think the best summary comes from David Wonnacott <davew@whuxm.uucp>: >without the ability of the compiler to use a derived class pointer >when a base class pointer is needed, or to use a derived class object to >initialize a base class reference, C++ would indeed provide very weak >object - oriented facilities. It does lack important functions like "in" >and "super", but they incur a storage and time overhead, so they are left >for the user to implement where needed. They are extremely hard to implement >(as you learned whey you tried), so you should look for a library that >provides them. Bjarne Stroustrup commented, of direct relevance to our requirement for C++ : >Are you aware of the task library as a C++ alternative to the >Simula class SIMULATION? Susan Coatney <coatney@venera.isi.edu> elaborated : >[the task library] is a package developed for discrete-event simulation >utilizing co-routine style multitasking. We weren't aware of the task library: the only information we had about C++ was the Book itself. Obviously, it is not the authorative source we had hoped (semi :-) We will obviously have to seek out whatever documentation came with the C++ sources [ we are running 1.1, and have 1.2 awaiting installation - I tried to install it on SUN 3/50s and BSD4.3, but failed miserably ] Bjarne's final suggestion was: >Have you seen my overview of C++ in SIGPLAN Notices Oct'86. Many people >find it a better introduction to the concepts in C++ than the book >itself. Now we know about it, we will certainly look it up. Finally, we are very interested in any further [private or public] communication about the suitability of C++ for simulation. Thanks to everyone who responded for all the help... our apologies for the obvious boo-boo. -- Duncan White & Phil Hands. ----------------------------------------------------------------------------- JANET address : dcw@uk.ac.ic.doc| Snail Mail : Duncan White, --------------------------------| Dept of Computing, This space intentionally | Imperial College, left blank...... | 180 Queen's Gate, (paradoxical excerpt from | South Kensington, IBM manuals) | London SW7 ---------------------------------------------------------------------------- Tel: UK 01-589-5111 x 4982/4991 ----------------------------------------------------------------------------
mat@mtx5a.UUCP (m.terribile) (06/14/87)
This is one more voice in a large discussion. > > However, SIMULA allows an important 'other' style of initialization : > > > >> v = new car; > > > > which initialises v, but not to a vehicle-instance: to a car-instance [ car > > must be a subclass of vehicle ] > > > > C++ objects [vehemently] to this kind of use : probably because it has many > > implications for run-time behaviour. > > No. C++ is excellent for doing this sort of thing, but requires a bit of > practice. E.g: As noted, the use of an object of class *derived* in place of an object of type *base* is allowed (so long as the base class is public) and is one of C++'s main OOP features. And yes, there *are* many implications for runtime behaviour and they are all good. Because of the static typing structure (more on this later) there are no runtime checks necessary and the runtime costs are quite well contained. See the last couple paragraphs of 7.2.8 in The Book (first edition.) > > o Associated operators like 'in' and 'qua' [ see below ] aren't available > > ... > > > Explanation of 'in' and 'qua' : > > ----------------------------- > > > > 1). The boolean SIMULA function : > > > > i in c > > > > returns true if the instance, i, is an instance of the given class, c, > > or if i is an instance of any subclass of c [ false otherwise ] In C++, this information is known at compile time. If the object represented by *i* is not of a type derived from type *c*, you simply cannot use it as such. Excplicit type coercion (casting) overrides this limitation. This also protects you from attempting to use a method that will be unavailable at runtime -- a very desirable property for real-world software. > > whereas this is ok: > > > >> print (v qua car).wheels; > > If we had: > class Car : Vehicle { > public: > int wheels() { return 4; } > int refuel(); > }; > > then the following could be done: > > Vehicle *v = (Vehicle*) new Car; > > ((Car*)v)->refuel(); > > Note that explicit casting is necessary to initialise v, since here Car does > not have Vehicle as a public base class. Similarly, to access Car::fuel, a > further explicit cast is required. Needless to say, such constructions are > an indication of poor structure, and should be avoided as much as possible. Indeed. If we expect to ask how many wheels a vehicle has, the number of wheels ought to be a property of vehicles. > C++ does not use any features that require storing information of what types > a thing is derived from. This is for reasons of efficiency, and to adhere to > the basic philosophy that if something doesn't use a feature, it should not > have to pay for it. And the static-type structure has advantages for non-academic software, as noted above. > This is not to say the C++ is the final answer. For example, adding features > such as multiple inheritance, parametrized classes, exception handling, etc, > would be good. However, C++ is designed for high efficiency and these > features take time to develop under such circumstances. I find it interesting > how so much has already been added to C, which is considered quite a stable > language, without introducing serious backward incompatibility. The ongoing > enhancement of C++ seems to prove that it IS possible for a language to be > both reliable and stable, but also evolving. I've had the pleasure of meeting and chatting with Bjarne Stroustrup, and reading a number of papers that he has written. It seems clear to me from these that we are, in many ways, pushing the standard compile-link-debug environment very near its limits. As you say, the evolution of C++ must be slow if it is to be good. At present, the limits on that evolution seem to be 1) our ability to judge what is The Right Way To Do Something, and what is a merely a booby-trapped shortcut, 2) our current programming environments, and 3) the ability of one individual, no matter how gifted, to get X, Y, and Z done and working in a given amount of time. Would anyone care to examine #2 in a careful, stepwise way? -- from Mole End Mark Terribile (scrape .. dig ) mtx5b!mat (Please mail to mtx5b!mat, NOT mtx5a! mat, or to mtx5a!mtx5b!mat) (mtx5b!mole-end!mat will also reach me) ,.. .,, ,,, ..,***_*.
mikem@otc.OZ (Mike Mowbray) (06/17/87)
In article <964@vaxb.calgary.UUCP>, west@calgary.UUCP (Darrin West) says: >> > o Associated operators like 'in' and 'qua' [ see below ] aren't available >> >> These are not really needed. You achieve the same result in other ways. See >> below. > > I don't think you can make this blanket statement. There are a lot of > simulationists who would not like to live without it. :-) ? Earlier in my C++ "career", I wondered about this very thing, thinking it would be nice to know at run-time if an instance of class x was actually a class something_else. The thing that leads me to make the above "blanket statement" is that operators like in and qua are basically only needed is to enable you to *do* things based on what they return. I.e: the crucial thing (I believe) is to shift one's design focus from "what is xyz?" to "what do I want to do with xyz?" Then it becomes clear that virtual functions are perfect for the job. Someone else mentioned that it's not clear yet exactly what techniques are good techniques. "Run-time explicit type info" versus "virtual functions" are a classic case of this and I'm sure we haven't heard the last of it. By the way, if you haven't read Bjarne's paper on multiple-inheritance (delivered at EUUG in May I understand), get a copy somehow. It's fascinating. Not only can you have multiple base classes in the obvious sense, you can have virtual base classes (where the grandparent base part of two separate parent base classes is a single entity in the derived child class rather than two separate lots of grandparent). You can also derive (actually name-extend) from a pointer within the class or outside it (great for getting rid of all those dummy little member functions we all know and hate). I for one will be very interested to play with it if/when it becomes part of commercial C++. Mike Mowbray Systems Development Overseas Telecommunications Commission (Australia) UUCP: {seismo,mcvax}!otc.oz!mikem ACSnet: mikem@otc.oz
tomm@voodoo.UUCP (06/18/87)
In article <129@otc.OZ> mikem@otc.OZ (Mike Mowbray) writes: [ stuff deleted -tomm- ] >By the way, if you haven't read Bjarne's paper on multiple-inheritance >(delivered at EUUG in May I understand), get a copy somehow. ^^^^ This sounds very good. What or who is EUUG, and where can I get a copy of this paper? Thanks in advance, -- Tom Mackey (206) 342-1442 (wk) Boeing Computer Services ....uw-beaver!ssc-vax!voodoo!tomm M/S 03-73, P.O. Box 24346, Seattle, WA 98124-0346
keith@nih-csl.UUCP (06/19/87)
In article <129@otc.OZ>, mikem@otc.OZ (Mike Mowbray) writes: > In article <964@vaxb.calgary.UUCP>, west@calgary.UUCP (Darrin West) says: > > >> > o Associated operators like 'in' and 'qua' [ see below ] aren't available > >> > >> These are not really needed. You achieve the same result in other ways. See > >> below. > > > > I don't think you can make this blanket statement. There are a lot of > > simulationists who would not like to live without it. :-) ? > > Earlier in my C++ "career", I wondered about this very thing, thinking > it would be nice to know at run-time if an instance of class x was > actually a class something_else. The thing that leads me to make the above > "blanket statement" is that operators like in and qua are basically only > needed is to enable you to *do* things based on what they return. > The Simula "in" operator is, I believe, like Smalltalk's "isKindOf" method, i.e, it allows you to test an object to see if it is an instance of a particular class or of a derived class. IsKindOf is useful for checking the type of argument objects to virtual functions, even in a statically type checked language like C++. 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"). -- Keith Gorlen phone: (301) 496-5363 Building 12A, Room 2017 uucp: seismo!elsie!nih-csl!keith National Institutes of Health Bethesda, MD 20892
mikem@otc.OZ (Mike Mowbray) (06/21/87)
In article <363@voodoo.UUCP>, tomm@voodoo.UUCP (Tom Mackey) says: > >By the way, if you haven't read Bjarne's paper on multiple-inheritance > >(delivered at EUUG in May I understand), get a copy somehow. > ^^^^ > This sounds very good. What or who is EUUG, and where can I get a copy > of this paper? I presume EUUG is "European Unix-Users Group"? I don't know about availability in the USA (which is why I said to get a copy "somehow"). I believe it is (or will be) published in their proceedings. Perhaps someone more familiar with EUUG can shed more light ... ? By the way, it says very clearly in the abstract that multiple-inheritance is not yet considered part of standard C++, so we'll have to be patient. Mike Mowbray Systems Development Overseas Telecommunications Commission (Australia) UUCP: {seismo,mcvax}!otc.oz!mikem ACSnet: mikem@otc.oz
mikem@otc.OZ (Mike Mowbray) (06/21/87)
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 <stream.h> #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 <stream.h> 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
keld@diku.UUCP (Keld J|rn Simonsen) (06/21/87)
In article <363@voodoo.UUCP> tomm@voodoo.UUCP (Tom Mackey) writes: >In article <129@otc.OZ> mikem@otc.OZ (Mike Mowbray) writes: >[ stuff deleted -tomm- ] >>By the way, if you haven't read Bjarne's paper on multiple-inheritance >>(delivered at EUUG in May I understand), get a copy somehow. > ^^^^ >This sounds very good. What or who is EUUG, and where can I get a copy >of this paper? EUUG is the European UNIX systems User Group. A copy of the proceedings of the EUUG Helsinki conference can be obtained by writing: EUUG secretary Owles Hall, Buntingford Herts, SG9 9PL Great Britain telephone +44 763 73039 Fax: +44 763 73255 Eunet: euug@inset.uucp I am not sure of the price, but it should be in the neighbourhood of USD 50,- for USENIX members. Keld Simonsen, EUUG Conference Officer >Thanks in advance, >-- >Tom Mackey (206) 342-1442 (wk) >Boeing Computer Services ....uw-beaver!ssc-vax!voodoo!tomm >M/S 03-73, P.O. Box 24346, Seattle, WA 98124-0346
bs@alice.UUCP (06/24/87)
> We weren't aware of the task library: the only information we had about C++ > was the Book itself. Obviously, it is not the authorative source we had > hoped (semi :-)'' It is, I hope, authorative, but naturally cannot be complete if by complete you mean ``contains a list of all usefull libraries of functions and classes''. The task library isn't part of the language it is a library. It is documented in the ``released notes'' that AT&T ships with the C++ tape. We clearly need a way of making libraries known and available to people that needs them. This is, however, a thorny administrative problem. Good luck. - Bjarne
bs@alice.UUCP (06/24/87)
It has been suggested in this series of articles that C++ lacks IN and SUPER.
``IN'' (and INSPECT) was deriberately not included (after much consideration
of my experience with SIMULA) and can as pointed out can easily be provided
by a programmer for a SPECIFIC tree of classes (either in a type secure and
often reasonably efficient manner using a virtual function or in an optimally
efficient and insecure manner using a cast).
C++ certainly has an equivallent to the SMALLTALK ``SUPER'' (which SIMULA
unfortunately lacks):
class B {
// ...
public:
virtual print() { cout << "B"; }
};
class D : public B {
// ...
public:
print() {
cout << "D : "; // do my stuff
B::print(); // let my base class B do its stuff
}
};
main()
{
D d;
d.print();
B* p = &d;
p->print();
}
will print ``D : B'' twice.
The reason that the notation ``name_of_base_class::'' is used rather than
``super'' is that this more explicit notation is general enough to cope with
the needs of multiple inheritance (multiple base classes):
class A { /* ... */ print(); };
class B { /* ... */ print(); };
class C : A, B {
// ...
print() { /* ... */ A::print(); B::print(); }
};
It should also be noted that most or all of the features of a ``more dynamic''
i.e. dynamically, but not statically, typed language can be provided as a
library in a statically typed language such as C++. Keith Gorlen's OOPS
library demonstrates that dramatically by providing the most fundamental
and useful non-graphical SMALLTALK classes for C++. (OOPS provides standard
``container classes'' such as dictonaries and sets, the ability to write an
object to disk and read it back again, SMALLTALK-like Is and IsKindOf, etc.).
The inverse operation, ``simulating'' a static type system for a language
that posesses only dynamic type checking is much harder (e.g. it is a popular
and very hard research problem to determine which part of a Smalltalk system
is used by a specific program - and in which way - to ensure completeness
and enable optimization).keith@nih-csl.UUCP (keith gorlen) (06/24/87)
In article <131@otc.OZ>, mikem@otc.OZ (Mike Mowbray) writes: > // *********************** typetst.c ************************************* > #include <stream.h> > > #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.) > //-------------------------------------------------------- This technique assumes that all instances of a class will have the same vtbl address, which is not true unless you use the +e compiler switch. Without this switch, each .c file that #includes the declarations of a class with virtual functions gets its own instance of vtbl, and objects constructed by code in two different .c files will have different vtbl addresses as a result, even though they may belong to the same class. Since using the +e switch is such a pain, I wouldn't recommend this method. > > 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: > My OOPS class library uses instances of class Class to describe the class hierarchy at run-time. The programmer must declare one, static, instance of class Class for each class he writes. Each instance of class Class contains a pointer to the instance of class Class that describes the base class, the class name as a character string, the size in bytes of instances of the class described, the address of a function for reading instances of the class from a stream, and other useful information. Each class defines the virtual function const Class* isA() to return the address of its Class descriptor instance. Also, the (static) constructor for class Class adds all the Class instances linked in the program into a Dictionary, keyed by the name of the class. This structure supports run-time type checking, object I/O, object copying operations, object I/O (i.e., storeOn(ostream&) and readFrom(istream&)), and allows class names to be printed in error messages. It has low overhead, and adds no additional member variables to objects. I've been using it for over a year, and am satisfied with it, except that I wish I had named the class something else! (The whole idea was stolen from Smalltalk-80) -- Keith Gorlen phone: (301) 496-5363 Building 12A, Room 2017 uucp: seismo!elsie!nih-csl!keith National Institutes of Health Bethesda, MD 20892
mat@mtx5a.UUCP (m.terribile) (06/26/87)
> > ... > > 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. ... More real-world usage of object-oriented techniques in critical > high-performance applications with large maintenance problems is > required. ... 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, ... (One objection to adding this feature to > C++ proper is the run-time overhead. ... > pointer which can in principle be used if you're desperate enough.) The If run-time overhead were an issue, the ``new'' and ``delete'' operators would not have been included. The issue is far more elemental. Why do we want to use the ``is'' test? Because we want to determine something about the type structure. That means that either 1) we have encoded some information in the type structure in a manner that is not a part of the language and that violates the supported paradigms, which were carefully designed to encourage good practices, or 2) We are relying on the incidental relationships within the type structure to provide us with information we should have represented explicitly. If you *need* a method invocation, program it, ferpeetsakes and make it explicit! -- from Mole End Mark Terribile (scrape .. dig ) mtx5b!mat (Please mail to mtx5b!mat, NOT mtx5a! mat, or to mtx5a!mtx5b!mat) (mtx5b!mole-end!mat will also reach me) ,.. .,, ,,, ..,***_*.
mikem@otc.OZ (Mike Mowbray) (07/02/87)
In article <227@nih-csl.UUCP>, keith@nih-csl.UUCP (keith gorlen) says: > Summary: Suggested technique for testing the class of an object > has some problems. > > In article <131@otc.OZ>, mikem@otc.OZ (Mike Mowbray) writes: >> >> #define IS_TYPE(classtype,instance) ((instance)._vptr == ::classtype\ >> __vtbl) >> [ ... deleted ... ] >> //-------------------------------------------------------- > > This technique assumes that all instances of a class will have the > same vtbl address, which is not true unless you use the +e compiler > switch. > [ ... deleted ... ] > Since using the +e switch is such a pain, I wouldn't recommend this method. Agreed. (Don't know why I mentioned this dubious method at all - I just thought it was an interesting curiosity.) Mike Mowbray Systems Development Overseas Telecommunications Commission (Australia) UUCP: {seismo,mcvax}!otc.oz!mikem ACSnet: mikem@otc.oz
weh@druhi.ATT.COM (HopkinsWE) (07/06/87)
In article <134@otc.OZ>, mikem@otc.OZ (Mike Mowbray) writes: > In article <227@nih-csl.UUCP>, keith@nih-csl.UUCP (keith gorlen) says: > > Since using the +e switch is such a pain, I wouldn't recommend this method. > > Agreed. If one is using virtual member functions in classes which appear in many different source files, I highly recommend using the +e option to CC to reduce the size of the object code significantly (the addendum to the release notes quotes a savings of 25%). A simple approach to using the +e option is to use +e0 (only include external reference to virtual table) for all .c files, and compile the .h files containing the class declarations using +e1 (generate globally accessible virtual tables). Wherever the .o files containing the class member functions are included, also include the .o containing the virtual tables. If they are placed in an archive (``library''), both will be automatically included by loader if the class is used anywhere. Thus, all .c files may be treated uniformly (all compiled with +e0 option); the only added work is to add a general rule for compiling .h files containing class declarations, a simple task if using make. Bill Hopkins AT&T 11900 N. Pecos St. Denver, CO 80234 {allegra|ihnp4}!druhi!weh P.S. Please note that the .h files will have to be temporarily copied or linked into a .c file; otherwise the c compiler won't compile it.
keith@nih-csl.UUCP (07/09/87)
In article <2002@druhi.ATT.COM>, weh@druhi.ATT.COM (HopkinsWE) writes: > In article <134@otc.OZ>, mikem@otc.OZ (Mike Mowbray) writes: > > In article <227@nih-csl.UUCP>, keith@nih-csl.UUCP (keith gorlen) says: > > > Since using the +e switch is such a pain, I wouldn't recommend this method. > > > > Agreed. > > A simple approach to using the +e option is to use +e0 (only include > external reference to virtual table) for all .c files, and compile > the .h files containing the class declarations using +e1 (generate > globally accessible virtual tables). This won't work on my .h files -- I usually have derived classes. For example, suppose you have a base class B with virtual functions declared in the file B.h and classes D1 and D2 derived from B declared in files D1.h and D2.h, respectively. Now to compile D1.h or D2.h, you must include the declaration of their base class B, so you might as well #include "B.h" in D1.h and D2.h. So if you compile both D1.h and D2.h with the +e1 option both D1.o and D2.o have global definitions of the vtbl for class B. Have I misunderstood something? -- Keith Gorlen phone: (301) 496-5363 Building 12A, Room 2017 uucp: seismo!elsie!nih-csl!keith National Institutes of Health Bethesda, MD 20892
crocker@ihwpt.ATT.COM (ron crocker) (07/10/87)
> > A simple approach to using the +e option is to use +e0 (only include > > external reference to virtual table) for all .c files, and compile > > the .h files containing the class declarations using +e1 (generate > > globally accessible virtual tables). > > This won't work on my .h files -- I usually have derived classes. > ... > Have I misunderstood something? Yes, I think so. You compile D1.c and D2.c with +e0. D1.o and D2.o then refer to some external vtbls, including the one for class B. You then compile a file (or set of files if there are some conflicts) containing only header files, call it headers.c, with +e1. Now, headers.o contains the definitions for the vtbls, and ld takes care of the rest. (This all is nonsense if I have misunderstood something. However, this is the mechanism I remember using, and I believe it is a reasonable representation of what Hopkins said.) > > -- > Keith Gorlen phone: (301) 496-5363 -- Ron Crocker AT&T Bell Laboratories Room IH 6D535 Naperville-Wheaton Road Naperville, IL 60566 (312) 979-4051
weh@druhi.UUCP (07/10/87)
In article <229@nih-csl.UUCP>, keith@nih-csl.UUCP (keith gorlen) writes: > In article <2002@druhi.ATT.COM>, weh@druhi.ATT.COM (HopkinsWE) writes: > > A simple approach to using the +e option is to use +e0 (only include > > external reference to virtual table) for all .c files, and compile > > the .h files containing the class declarations using +e1 (generate > > globally accessible virtual tables). > > This won't work on my .h files -- I usually have derived classes. For > example, suppose you have a base class B with virtual functions > declared in the file B.h and classes D1 and D2 derived from B declared > in files D1.h and D2.h, respectively. Now to compile D1.h or D2.h, > you must include the declaration of their base class B, so you might > as well #include "B.h" in D1.h and D2.h. So if you compile both D1.h > and D2.h with the +e1 option both D1.o and D2.o have global > definitions of the vtbl for class B. Have I misunderstood something? Keith's observation is correct. The simplistic solution I suggested is fatally flawed; there is no point in using the +e option WITHOUT iheritance and virtual member functions. After some thought, writing a test case, and consultation with other C++ developers, a reasonable solution does exist. In summary, a special .c file is used to include ALL class declarations which needs a virtual table. That file is compiled using +e1 while all the other .c files are compiled using +e0. The following example illustrates this: // base.h #ifndef base_h #define base_h class base { int one_int; public: base () { one_int = 0; } virtual int get_one_int () { return one_int; } }; #endif // derived.h #ifndef derived_h #define derived_h #include <stdio.h> #include "base.h" class derived: public base { int one_int; public: derived () { one_int = 1; } int get_one_int () { return one_int; } void display () { printf("derived::one_int = %d, base::one_int = %d\n", get_one_int (), base::get_one_int ()); } }; #endif // derived_2.h #ifndef derived_2_h #define derived_2_h #include <stdio.h> #include "base.h" class derived_2: public base { int one_int; public: derived_2 () { one_int = 2; } int get_one_int () { return one_int; } void display () { printf("derived_2::one_int = %d, base::one_int = %d\n", get_one_int (), base::get_one_int ()); } }; #endif // defs.c #include "base.h" #include "derived.h" #include "derived_2.h" // main.c #include "derived.h" #include "derived_2.h" main() { derived one_obj; derived_2 one_obj_2; one_obj.display(); one_obj_2.display(); } This compiled using the following commands: CC +e1 -c defs.c CC +e0 main.c defs.o And executes with the expected results: $ a.out derived::one_int = 1, base::one_int = 0 derived_2::one_int = 2, base::one_int = 0 The #ifndefs are necessary to ensure that each class is included once (not a bad practice to follow in general). This approach has been used in some medium sized projects, and the problem experienced is that the preprocessor and compiler tables are stressed. Bill Hopkins AT&T 11900 N. Pecos St. Denver, CO 80234 {allegra|ihnp4}!druhi!weh