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++ Message-ID: <125@otc.OZ> Date: Mon, 8-Jun-87 20:04:40 EDT Article-I.D.: otc.125 Posted: Mon Jun 8 20:04:40 1987 Date-Received: Fri, 12-Jun-87 01:47:28 EDT References: <452@ivax.doc.ic.ac.uk> Distribution: comp Organization: O.T.C. Systems Development, Australia Lines: 369 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 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