From rifkin@cco.caltech.edu Sun Feb 13 05:15:35 1994 To: adam@vlsi.cs.caltech.edu Subject: C++ Faq 2 >Newsgroups: comp.lang.c++ >Path: nntp-server.caltech.edu!netline-fddi.jpl.nasa.gov!elroy.jpl.nasa.gov!swrinde!cs.utexas.edu!howland.reston.ans.net!europa.eng.gtefsd.com!news.umbc.edu!eff!news.kei.com!ub!clarkson!cheetah.ece.clarkson.edu!cline >From: cline@cheetah.ece.clarkson.edu (Marshall Cline) >Subject: C++ FAQ: posting #2/4 >Message-ID: <1994Feb11.210625.167@news.clarkson.edu> >Followup-To: comp.lang.c++ >Summary: Please read this before posting to comp.lang.c++ >Sender: cline@sun.soe.clarkson.edu >Nntp-Posting-Host: cheetah.ece.clarkson.edu >Reply-To: cline@parashift.com (Marshall Cline) >Organization: Paradigm Shift, Inc (training/OOD/C++/libraries) >Date: Fri, 11 Feb 1994 21:06:25 GMT >Expires: Fri, 11 Mar 1994 21:06:25 GMT >Lines: 920 comp.lang.c++ Frequently Asked Questions list (with answers, fortunately). Copyright (C) 1991-93 Marshall P. Cline, Ph.D. Posting 2 of 4. Posting #1 explains copying permissions, (no)warranty, table-of-contents, etc ============================================================================== SECTION 8: Freestore management ============================================================================== Q29: Does `delete ptr' delete the ptr or the pointed-to-data? A: The pointed-to-data. When you read `delete p', say to yourself `delete the thing pointed to by p'. One could argue that the keyword is misleading, but the same abuse of English occurs when `free'ing the memory pointed to by a ptr in C: free(ptr); /* why not `free_the_stuff_pointed_to_by(p)' ?? */ ============================================================================== Q30: Can I free() ptrs alloc'd with `new' or `delete' ptrs alloc'd w/ malloc()? A: No. You should not mix C and C++ heap management. ============================================================================== Q31: Why should I use `new' instead of trustworthy old malloc()? A: malloc() doesn't call constructors, and free() doesn't call destructors. Besides, malloc() isn't type safe, since it returns a `void*' rather than a ptr of the right type (ANSI-C punches a hole in its typing system to make it possible to use malloc() without pointer casting the return value, but C++ closes that hole). Besides, `new' is an operator that can be overridden by a class, while `malloc' is not overridable on a per-class basis (ie: even if the class doesn't have a constructor, allocating via malloc might do inappropriate things if the freestore operations have been overridden). ============================================================================== Q32: Why doesn't C++ have a `realloc()' along with `new' and `delete'? A: Because realloc() does *bitwise* copies (when it has to copy), which will tear most C++ objects to shreds. C++ objects know how to copy themselves. They use their own copy constructor or assignment operator (depending on whether we're copying into a previously unused space [copy-ctor] or a previous object [assignment op]). Moral: never use realloc() on objects of a class. Let the class copy its own objects. ============================================================================== Q33: How do I allocate / unallocate an array of things? A: Use new[] and delete[]: Thing* p = new Thing[100]; //... delete [] p; //older compilers require you to use `delete [100] p' Any time you allocate an array of things (ie: any time you use the `[...]' in the `new' expression) you *!*MUST*!* use the `[]' in the `delete' statement. The fact that there is no syntactic difference between a ptr to a thing and a ptr to an array of things is an artifact we inherited from C. ============================================================================== Q34: What if I forget the `[]' when `delete'ing array allocated via `new X[n]'? A: Life as we know it suddenly comes to a catastrophic end. It is the programmer's --not the compiler's-- responsibility to get the connection between new[] and delete[] correct. If you get it wrong, neither a compile-time nor a run-time error message will be generated by the compiler. Heap corruption is a likely result. ============================================================================== SECTION 9: Debugging and error handling ============================================================================== Q35: How can I handle a constructor that fails? A: Constructors (ctors) do not return any values, so no returned error code is possible. The best way to handle failure is therefore to `throw' an exception. If your compiler doesn't yet support exceptions, several possibilities remain. The simplest is to put the object itself into a `half baked' state by setting an internal status bit. Naturally there should be a query (`inspector') method to check this bit, allowing clients to discover whether they have a live object. Other member functions should check this bit, and either do a no-op (or perhaps something more obnoxious such as `abort()') if the object isn't really alive. Check out how the iostreams package handles attempts to open nonexistent/illegal files for an example of prior art. ============================================================================== Q36: How can I compile-out my debugging print statements? A: This will NOT work, since comments are parsed before the macro is expanded: #ifdef DEBUG_ON #define DBG #else #define DBG // #endif DBG cout << foo; This is the simplest technique: #ifdef DEBUG_ON #define DBG(anything) anything #else #define DBG(anything) /*nothing*/ #endif Then you can say: //... DBG(cout << "the value of foo is " << foo << '\n'); // ^-- `;' outside () Any commas in your `DBG()' statement must be enclosed in a `()': DBG(i=3, j=4); //<---- C-preprocessor will generate error message DBG(i=3; j=4); //<---- ok There are also more complicated techniques that use variable argument lists, but these are primarily useful for `printf()' style (see question on the pros and cons of as opposed to for more). ============================================================================== SECTION 10: Const correctness ============================================================================== Q37: What is `const correctness'? A: A program is `const correct' if it never mutates a constant object. This is achieved by using the keyword `const'. Ex: if you pass a String to a function `f()', and you wish to prohibit `f()' from modifying the original String, you can either pass by value: void f( String s ) { /*...*/ } or by constant reference: void f(const String& s ) { /*...*/ } or by constant pointer: void f(const String* sptr) { /*...*/ } but *not* by non-const ref: void f( String& s ) { /*...*/ } *nor* by non-const pointer: void f( String* sptr) { /*...*/ } Attempted changes to `s' within a fn that takes a `const String&' are flagged as compile-time errors; neither run-time space nor speed is degraded. ============================================================================== Q38: Is `const correctness' a good goal? A: Declaring the `constness' of a parameter is just another form of type safety. It is almost as if a constant String, for example, `lost' its various mutative operations. If you find type safety helps you get systems correct (especially large systems), you'll find const correctness helps also. Short answer: yes, const correctness is a good goal. ============================================================================== Q39: Is `const correctness' tedious? A: Type safety requires you to annotate your code with type information. In theory, expressing this type information isn't necessary -- witness untyped languages as an example of this. However in practice, programmers often know in their heads a lot of interesting information about their code, so type safety (and, by extension, const correctness) merely provide structured ways to get this information into their keyboards. Short answer: yes, const correctness is tedious. ============================================================================== Q40: Should I try to get things const correct `sooner' or `later'? A: Back-patching const correctness is *very* expensive. Every `const' you add `over here' requires you to add four more `over there'. The snowball effect is magnificent -- unless you have to pay for it. Long about the middle of the process, someone stumbles on a function that needs to be const but can't be const, and then they know why their system wasn't functioning correctly all along. This is the benefit of const correctness, but it should be installed from the beginning. Short answer: CONST CORRECTNESS SHOULD NOT BE DONE RETROACTIVELY!! ============================================================================== Q41: What is a `const member function'? A: A const member function is a promise to the caller not to change the object. Put the word `const' after the member function's signature; ex: class X { //... void f() const; }; Some programmers feel this should be a signal to the compiler that the raw bits of the object's `struct' aren't going to change, others feel it means the *abstract* (client-visible) state of the object isn't going to change. C++ compilers aren't allowed to assume the bitwise const, since a non-const alias could exist which could modify the state of the object (gluing a `const' ptr to an object doesn't promise the object won't change; it only promises that the object won't change **via that pointer**). I talked to Jonathan Shopiro at the C++AtWork conference, and he confirmed that the above view has been ratified by the ANSI-C++ standards board. This doesn't make it a `perfect' view, but it will make it `the standard' view. See the next few questions for more. ============================================================================== Q42: What is an `inspector'? What is a `mutator'? A: An inspector inspects and a mutator mutates. These different categories of member fns are distinguished by whether the member fn is `const' or not. ============================================================================== Q43: What is `casting away const in an inspector' and why is it legal? A: In current C++, const member fns are allowed to `cast away the const-ness of the "this" ptr'. Programmers use (some say `misuse') this to tickle internally used counters, cache values, or some other non-client-visible change. Since C++ allows you to use const member fns to indicate the abstract/meaning-wise state of the object doesn't change (as opposed to the concrete/bit-wise state), the `meaning' of the object shouldn't change during a const member fn. Those who believe `const' member fns shouldn't be allowed to change the bits of the struct itself call the `abstract const' view `Humpty Dumpty const' (Humpty Dumpty said that words mean what he wants them to mean). The response is that a class' public interface *should* mean exactly what the class designer wants it to mean, in Humpty Dumpty's words, `nothing more and nothing less'. If the class designer says that accessing the length of a List doesn't change the List, then one can access the length of a `const' List (even though the `len()' member fn may internally cache the length for future accesses). Some proposals are before the ANSI/ISO C++ standards bodies to provide syntax that allows individual data members to be designated as `can be modified in a const member fn' using a prefix such as `~const'. This would blend the best of the `give the compiler a chance to cache data across a const member fn', but only if aliasing can be solved (see next question). ============================================================================== Q44: But doesn't `cast away const' mean lost optimization opportunities? A: If the object is constructed in the scope of the const member fn invocation, and if all the non-const member function invocations between the object's construction and the const member fn invocation are statically bound, and if every one of these invocations is also `inline'd, and if the ctor itself is `inline', and if any member fns the ctor calls are inline, then the answer is `Yes, the soon-to-be-standard interpretation of the language would prohibit a very smart compiler from detecting the above scenario, and the register cache would be unnecessarily flushed'. The reader should judge whether the above scenario is common enough to warrant a language change which would break existing code. ============================================================================== SECTION 11: Inheritance ============================================================================== Q45: What is inheritance? A: Inheritance is what separates abstract data type (ADT) programming from OOP. It is not a `dark corner' of C++ by any means. In fact, everything discussed so far could be simulated in your garden variety ADT programming language (ex: Ada, Modula-2, C [with a little work], etc). Inheritance and the consequent (subclass) polymorphism are the two big additions which separate a language like Ada from an object-oriented programming language. ============================================================================== Q46: Ok, ok, but what is inheritance? A: Human beings abstract things on two dimensions: part-of and kind-of. We say that a Ford Taurus is-a-kind-of-a Car, and that a Ford Taurus has parts such as Engine, Tire, etc. The part-of hierarchy has been a first class part of software since the ADT style became relevant, but programmers have had to whip up their own customized techniques for simulating kind-of (usually in an ad hoc manner). Inheritance changes that; it adds `the other' major dimension of decomposition. An example of `kind-of decomposition', consider the genus/species biology charts. Knowing the internal parts of various fauna and flora is important for certain applications, but knowing the groupings (kinds, categories) is equally important. ============================================================================== Q47: How do you express inheritance in C++? A: By the `: public' syntax: class Car : public Vehicle { //^^^^^^^^---- `: public' is pronounced `is-a-kind-of-a' //... }; We state the above relationship in several ways: * Car is `a kind of a' Vehicle * Car is `derived from' Vehicle * Car is `a specialized' Vehicle * Car is the `subclass' of Vehicle * Vehicle is the `base class' of Car * Vehicle is the `superclass' of Car (this not as common in the C++ community) ============================================================================== Q48: What is `incremental programming'? A: In addition to being an abstraction mechanism that makes is-a-kind-of relationships explicit, inheritance can also be used as a means of `incremental programming'. A derived class inherits all the representation (bits) of its base class, plus all the base class' mechanism (code). Another device (virtual functions, described below) allows derived classes to selectively override some or all of the base class' mechanism (replace and/or enhance the various algorithms). This simple ability is surprisingly powerful: it effectively adds a `third dimension' to programming. After becoming fluent in C++, most programmers find languages like C and Ada to be `flat' (a cute little book, `Flatland', aptly describes those living in a two dimensional plane, and their disbelief about a strange third dimension that is somehow neither North, South, East nor West, but is `Up'). As a trivial example, suppose you have a Linked List that is too slow, and you wish to cache its length. You could `open up' the List `class' (or `module'), and modify it directly (which would certainly be appropriate for such a simple situation), but suppose the List's physical size is critical, and some important client cannot afford to add the extra machine word to every List. Another option would be to textually copy the List module and modify the copy, but this increases the amount of code that must be maintained, and also presumes you have access to the internal source code of the List module. The OO solution is to realize that a List that caches its length is-a-kind-of-a List, so we inherit: class FastList : public List { public: //override operations so the cache stays `hot' protected: int length; //cache the length here }; ============================================================================== Q49: Should I pointer-cast from a derived class to its base class? A: The short answer: yes -- you don't even need the `cast'. Long answer: a derived class is a specialized version of the base class (`Derived is-a-kind-of-a Base'). The upward conversion is perfectly safe, and happens all the time (a ptr to a Derived is in fact pointing to a [specialized version of a] Base): void f(Base* base_ptr); void g(Derived* derived_ptr) { f(derived_ptr); } //perfectly safe; no cast (note that the answer to this question assumes we're talking about `public' derivation; see below on `private/protected' inheritance for `the other kind'). ============================================================================== Q50: Derived* --> Base* works ok; why doesn't Derived** --> Base** work? A: A C++ compiler will allow a Derived* to masquerade as a Base*, since a Derived object is a kind of a Base object. However passing a Derived** as a Base** (or otherwise trying to convert a Derived** to a Base**) is (correctly) flagged as an error. An array of Deriveds is-NOT-a-kind-of-an array of Bases. I like to use the following example in my C++ training sessions: `A Bag of Apples is *NOT* a Bag of Fruit' Suppose a `Bag' could be passed to a function taking a Bag such as `f(Bag& b)'. But `f()' can insert *any* kind of Fruit into the Bag. Imagine the surprise on the caller's face when he gets the Bag back only to find it has a Banana in it! Here's another example I use: A ParkingLot of Car is-NOT-a-kind-of-a ParkingLot of Vehicle (otherwise you could pass a ParkingLot* as a ParkingLot*, and the called fn could park an EighteenWheeler in a ParkingLot designed for Cars!) These improper things are violations of `contravariance' (that's the scientific glue that holds OOP together). C++ enforces contravariance, so you should trust your compiler at moments like these. Contravariance is more solid than our fickle intuition. ============================================================================== Q51: Does array-of-Derived is-NOT-a-kind-of array-of-Base mean arrays are bad? A: Yes, `arrays are evil' (jest kidd'n :-). There's a very subtle problem with using raw built-in arrays. Consider this: void f(Base* array_of_Base) { array_of_Base[3].memberfn(); } main() { Derived array_of_Derived[10]; f(array_of_Derived); } This is perfectly type-safe, since a D* is-a B*, but it is horrendously evil, since Derived might be larger than Base, so the array index in f() not only isn't type safe, it's not even going to be pointing at a real object! In general it'll be pointing somewhere into the innards of some poor D. The fundamental problem here is that C++ cannot distinguish a ptr-to-a-thing from a ptr-to-an-array-of-things (witness the required `[]' in `delete[]' when deleting an array as another example of how these different kinds of ptrs are actually different). Naturally C++ `inherited' this feature from C. This underscores the advantage of using an array-like *class* instead of using a raw array (the above problem would have been properly trapped as an error if we had used a `Vec' rather than a `T[]'; ex: you cannot pass a Vec to `f(Vec& v)'). ============================================================================== SUBSECTION 11A: Inheritance -- virtual functions ============================================================================== Q52: What is a `virtual member function'? A: A virtual member function is a member fn preceded by the keyword `virtual'. It has the effect of allowing derived classes to replace the implementation of the fn. Furthermore the replacement is always called whenever the object in question is actually of the derived class. The impact is that algorithms in the base class can be replaced in the derived class without affecting the operation of the base class. The replacement can be either full or partial, since the derived class operation can invoke the base class version if desired. This is discussed further below. ============================================================================== Q53: What is dynamic dispatch? Static dispatch? A: In the following discussion, `ptr' means either a pointer or a reference. When you have a ptr to an object, there are two distinct types in question: the static type of the ptr, and the dynamic type of the pointed-to object (the object may actually be of a class that is derived from the class of the ptr). The `legality' of the call is checked based on the static type of the ptr, which gives us static type safety (if the type of the ptr can handle the member fn, certainly the pointed-to object can handle it as well, since the pointed-to object is of a class that is derived from the ptr's class). Suppose ptr's type is `List' and the pointed-to object's type is `FastList'. Suppose the fn `len()' is provided in `List' and overridden in `FastList'. The question is: which function should actually be invoked: the function attached to the pointer's type (`List::len()') or the function attached to the object itself (`FastList::len()')? If `len()' is a virtual function, as it would be in the above case, the fn attached to the object is invoked. This is called `dynamic binding', since the actual code being called is determined dynamically (at run time). On the other hand, if `len()' were non-virtual, the dispatch would be resolved statically to the fn attached to the ptr's class. ============================================================================== Q54: Can I override a non-virtual fn? A: Yes but you shouldn't. The only time you should do this is to get around the `hiding rule' (see below, and ARM sect.13.1), and the overridden definition should be textually identical to the base class' version. The above advice will keep you out of trouble, but it is a bit too strong. Experienced C++ programmers will sometimes override a non-virtual fn for efficiency, and will provide an alternate implementation which makes better use of the derived class' resources. However the client-visible effects must be *identical*, since non-virtual fns are dispatched based on the static type of the ptr/ref rather than the dynamic type of the pointed-to/referenced object. ============================================================================== Q55: Why do I get the warning "Derived::foo(int) hides Base::foo(double)"? A: A member function in a derived class will *hide* all member functions of the same name in the base class, *not* overload them, even if Base::foo(double) is virtual (see ARM 13.1). This is done because it was felt that programmers would, for example, call a_derived.foo(1) and expect Derived::foo(double) to be called. If you define any member function with the name `foo' in a derived class, you must redefine in class Derived all other Base::foo()'s that you wish to allow access from a Derived object (which generally means all of them; you should [generally] *not* try to hide inherited public member functions since it breaks the `conformance' of the derived class with respect to the base class). class Base { public: void foo(int); }; class Derived : public Base { public: void foo(double); void foo(int i) { Base::foo(i); } // <-- override it with itself }; ============================================================================== SUBSECTION 11B: Inheritance -- conformance ============================================================================== Q56: Can I `revoke' or `hide' public member fns inherited from my base class? A: Never never never do this. Never. NEVER! This is an all-too-common design error. It usually stems from muddy thinking (but sometimes it stems from a very difficult design that doesn't seem to yield anything elegant). ============================================================================== Q57: Is a `Circle' a kind-of an `Ellipse'? A: Depends on what you claim an Ellipse can do. Ex: suppose Ellipse has a `scale(x,y)' method, which is meaningless for Circle. There are no easy options at this point, but the worst of all possible worlds is to keep muddling along and hope that no one stubs their toes over the bad design (if we're serious about reuse, we should fix our mistakes rather than leave them to a future generation). If an Ellipse can do something a Circle can't, a Circle can't be a kind of Ellipse. Should there be any other relationship between Circle and Ellipse? Here are two reasonable options: * make Circle and Ellipse completely unrelated classes. * derive Circle and Ellipse from a base class representing `Ellipses that can't *necessarily* perform an unequal-scale operation'. In the first case, Ellipse could be derived from class `AsymmetricShape' (with scale(x,y) being introduced in AsymmetricShape), and Circle should be derived from `SymmetricShape', which has a scale(factor) member fn. In the second case, we could create class `Oval' that has only an equal scale operation, then derive both `Ellipse' and `Circle' from Oval, where Ellipse --but not Circle-- adds the unequal scale operation (see the `hiding rule' for a caveat if the same method name `scale' is used for both unequal and equal scale operations). In any event, we could create an operation to create an Ellipse whose size et al are the same as a given Circle, but this would be a constructive operation (ie: it would create a brand new object, like converting an int to a float, but unlike passing a reference to a Circle as if it were a ref to an Ellipse). Example: class Ellipse : public Oval { public: ^^^^^^^^^^^^^---- or whatever Ellipse(const Circle& circle); //... }; Or: class Circle /*...*/ { public: operator Ellipse() const; //... }; ============================================================================== Q58: Are there other options to the `Circle is/isnot kind-of Ellipse' dilemma? A: There appear to be 2 other options (but read below for why these are poor): * redefine Circle::scale(x,y) to throw an exception or call `abort()'. * redefine Circle::scale(x,y) to be a no-op, or to scale both dimensions by the average of the parameters (or some other arbitrary value). Throwing an exception will `surprise' clients. You claimed that a Circle was actually a kind of an Ellipse, so they pass a Circle off to `f(Ellipse& e)'. The author of this function read the contract for Ellipse very carefully, and scale(x,y) is definitely allowed. Yet when f() innocently calls e.scale(5,3), it kills him! Conclusion: you lied; what you gave them was distinctly *not* an Ellipse. In the second case, you'll find it to be very difficult to write a meaningful semantic specification (a `contract') for Ellipse::scale(x,y). You'd like to be able to say it scales the x-axis by `x' and the y-axis by `y', but the best you can say is `it may do what you expect, or it may do nothing, or it may scale both x and y even if you asked it to only scale the x (ex: `scale(2,1)'). Since you've diluted the contract into dribble, the client can't rely on any meaningful behavior, so the whole hierarchy begins to be worthless (it's hard to convince someone to use an object if you have to shrug your shoulders when asked what the object does for them). ============================================================================== SUBSECTION 11C: Inheritance -- access rules ============================================================================== Q59: Why can't I access `private' things in a base class from a derived class? A: Derived classes do not get access to private members of a base class. This effectively `seals off' the derived class from any changes made to the private members of the base class. ============================================================================== Q60: What's the difference between `public:', `private:', and `protected:'? A: `Private' is discussed in the previous section, and `public' means `anyone can access it'. The third option, `protected', makes a member (either data member or member fn) accessible to subclasses. Thus members defined in the `private:' section of class X are accessible only to the member functions and friends of class X; members defined in the `public:' section are accessible by everyone; `protected:' members are accessible by members fns and friends of class X, as well as member fns of subclasses of X. ============================================================================== Q61: How can I protect subclasses from breaking when I change internal parts? A: You can make your software more resilient to internal changes by realizing a class has two distinct interfaces for two distinct sets of clients: * its `public:' interface serves unrelated classes * its `protected:' interface serves derived classes A class that is intended to have a long and happy life can hide its physical bits in its `private:' part, then put `protected:' inline access functions to these data. The private bits can change, but if the protected access fns are stable, subclasses (ie: derived classes) won't break (though they'll need to be recompiled after a change to the base class). ============================================================================== SUBSECTION 11D: Inheritance -- constructors and destructors ============================================================================== Q62: Why does base ctor get *base*'s virtual fn instead of the derived version? Ie: when constructing an obj of class `Derived', Base::Base() invokes `virt()'. `Derived::virt()' exists (an override of `Base::virt()'), yet `Base::virt()' gets control rather than the `Derived' version; why? A: A constructor turns raw bits into a living object. Until the ctor has finished, you don't have a complete `object'. In particular, while the base class' ctor is working, the object isn't yet a Derived class object, so the call of the base class' virtual fn defn is correct. Similarly dtors turn a living object into raw bits (they `blow it to bits'), so the object is no longer a Derived during Base's dtor. Therefore the same thing happens: when Base::~Base() calls `virt()', Base::virt() gets control, not the Derived::virt() override. (Think of what would happen if the Derived fn touched a subobject from the Derived class, and you'll quickly see the wisdom of the approach). ============================================================================== Q63: Does a derived class dtor need to explicitly call the base destructor? A: No, you never need to explicitly call a dtor (where `never' means `rarely'). ie: you only have to have an explicit dtor call in rather esoteric situations such as destroying an object created by the `placement new operator'. In the usual case, a derived class' dtor (whether you explicitly define one or not) automatically invokes the dtors for subobjects and base class(es). Subobjects are destroyed immediately after the derived class' destructor body (`{...}'), and base classes are destroyed immediately after subobjects. Subobjects are destroyed from bottom to top in the lexical order they appear within a class, and base classes from right to left in the order of the base-class-list. ============================================================================== SUBSECTION 11E: Inheritance -- private and protected inheritance ============================================================================== Q64: How do you express `private inheritance'? A: When you use `: private' instead of `: public'. Ex: class Foo : private Bar { //... }; ============================================================================== Q65: How are `private derivation' and `containment' similar? dissimilar? A: Private derivation can be thought of as a syntactic variant of containment (has-a). Ex: it is NOT true that a privately derived is-a-kind-of-a Base: With private derivation: class Car : private Engine {/*...*/}; //a Car is NOT a-kind-of Engine Similarly: class Car { Engine e; /*...*/ }; //normal containment There are several similarities between these two forms of containment: * in both cases there is exactly one Engine subobject contained in a Car * in neither case can clients (outsiders) convert a Car* to an Engine* There are also several distinctions: * the second form is needed if you want to contain several subobjects * the first form can introduce unnecessary multiple inheritance * the first form allows members of Car to convert a Car* to an Engine* * the first form allows access to the `protected' members of the base class * the first form allows Car to override Engine's virtual functions. Private inheritance is almost always used for the last item: to gain access into the `protected:' members of the base class. ============================================================================== Q66: Should I pointer-cast from a `privately' derived class to its base class? A: The short answer: no, but yes too (better read the long answer!) >From `inside' the privately derived class (ie: in the body of members or friends of the privately derived class), the relationship to the base class is known, and the upward conversion from PrivatelyDer* to Base* (or PrivatelyDer& to Base&) is safe and doesn't need a cast. >From `outside' the privately derived class, the relationship to `Base' is a `private' decision of `PrivatelyDer', so the conversion requires a cast. Clients should not exercise this cast, since private derivation is a private implementation decision of the privately derived class, and the coercion will fail after the privately derived class privately chooses to change this private implementation decision. Bottom line: only a class and its friends have the right to convert a ptr to a derived class into a ptr to its private base class. They don't need a cast, since the relationship with the base class is accessible to them. No one else can convert such ptrs without pointer-casts, so no one else should. ============================================================================== Q67: Should I pointer-cast from a `protected' derived class to its base class? A: Protected inheritance is similar to private inheritance; the answer is `no'. class Car : protected Engine {/*...*/}; //protected inheritance In `private' inheritance, only the class itself (and its friends) can know about the relation to the base class (the relationship to the base class is a `private' decision). In protected inheritance, the relationship with the base class is a `protected' decision, so *subclasses* of the `protectedly' derived class can also know about and exploit this relationship. This is a `for better *and* for worse' situation: future changes to `protected' decisions have further consequences than changing a private decision (in this case, the class, its friends, *and* subclasses, sub- sub- classes, etc, all need to be examined for dependencies upon the relationship to the base class). However it is also for better, in that subclasses have the ability to exploit the relationship. The existence of protected inheritance in C++ is debated in some circles. ============================================================================== Q68: What are the access rules with `private' and `protected' inheritance? A: Take these classes as examples: class B { /*...*/ }; class D_priv : private B { /*...*/ }; class D_prot : protected B { /*...*/ }; class D_publ : public B { /*...*/ }; class Client { B b; /*...*/ }; Public and protected parts of B are `private' in D_priv, and are `protected' in D_prot. In D_publ, public parts of B are public (D_prot is-a-kind-of-a B), and protected parts of B remain protected in D_publ. Naturally *none* of the subclasses can access anything that is private in B. Class `Client' can't even access the protected parts of B (ie: it's `sealed off'). It is often the case that you want to make some but not all inherited member functions public in privately/protectedly derived classes. Ex: to make member fn B::f(int,char,float) public in D_prot, you would say: class D_prot : protected B { //... public: B::f; //note: not B::f(int,char,float) }; There are limitations to this technique (can't distinguish overloaded names, and you can't make a feature that was `protected' in the base `public' in the derived). Where necessary, you can get around these by a call-through fn: class D_prot : protected B { public: short f(int i, char c, float f) { return B::f(i,c,f); } }; ============================================================================== Q69: Do most C++ programmers use containment or private inheritance? A: Short answer: generalizations are always wrong (that's a generalization :-). The long answer is another generalization: most C++ programmers use regular containment (also called `composition' or `aggregation') more often than private inheritance. The usual reason is that they don't *want* to have access to the internals of too many other classes. Private inheritance is not evil; it's just more expensive to maintain, since it increases the number of classes that have access to `internal' parts of other classes (coupling). The `protected' parts of a class are more likely to change than the `public' parts. ============================================================================== SECTION 12: Abstraction ============================================================================== Q70: What's the big deal of separating interface from implementation? A: Separating interface from implementation is a key to reusable software. Interfaces are a company's most valuable resources. Designing an interface takes longer than whipping together a concrete class which fulfills that interface. Furthermore interfaces require the resources of more expensive people (for better and worse, most companies separate `designers' from `coders'). Since they're so valuable, they should be protected from being tarnished by data structures and other artifacts of the implementation (any data structures you put in a class can never be `revoked' by a derived class, which is why you want to `separate' the interface from the implementation). ============================================================================== Q71: How do I separate interface from implementation in C++ (like Modula-2)? A: Short answer: use an ABC (see next question for what an ABC is). ============================================================================== Q72: What is an ABC (`abstract base class')? A: An ABC corresponds to an abstract concept. If you asked a Mechanic if he repaired Vehicles, he'd probably wonder what *kind* of Vehicle you had in mind. Chances are he doesn't repair space shuttles, ocean liners, bicycles, and volkswaggon beetles too. The problem is that the term `Vehicle' is an abstract concept; you can't build one until you know what kind of vehicle to build. In C++, you'd make Vehicle be an ABC, with Bicycle, SpaceShuttle, etc, being subclasses (an OceanLiner is-a-kind-of-a Vehicle). In real-world OOP, ABCs show up all over the place. Technically, an ABC is a class that has one or more pure virtual member functions (see next question). You cannot make an object (instance) of an ABC. ============================================================================== Q73: What is a `pure virtual' member function? A: Some member functions exist in concept, but can't have any actual defn. Ex: Suppose I asked you to draw a Shape at location (x,y) that has size 7.2. You'd ask me `what kind of shape should I draw', since circles, squares, hexagons, etc, are drawn differently. In C++, we indicate the existence of the `draw()' method, but we recognize it can only be defined in subclasses: class Shape { public: virtual void draw() const = 0; //... ^^^--- `=0' means it is `pure virtual' }; This pure virtual makes `Shape' an ABC. The `const' says that invoking the `draw()' method won't change the Shape object (ie: it won't move around on the screen, change sizes, etc). If you want, you can think of it as if the code were at the NULL pointer. Pure virtuals allow you to express the idea that any actual object created from a [concrete] class derived from the ABC *will* have the indicated member fn, but we simply don't have enough information to actually *define* it yet. They allow separation of interface from implementation, which ultimately allows functionally equivalent subclasses to be produced that can `compete' in a free market sense (a technical version of `market driven economics'). ============================================================================== Q74: How can I provide printing for an entire hierarchy rooted at `class X'? A: Provide a friend operator<< that calls a protected virtual function: class X { public: friend ostream& operator<< (ostream& o,const X& x) { x.print(o); return o; } //... protected: virtual void print(ostream& o) const; //or `=0;' if `X' is abstract }; Now all subclasses of X merely provide their own `print(ostream&)const' member function, and they all share the common `<<' operator. Friends don't bind dynamically, but this technique makes them *act* as if they were. ============================================================================== Q75: What is a `virtual destructor'? A: In general, a virtual fn means to start at the class of the object itself, not the type of the pointer/ref (`do the right thing based on the actual class of' is a good way to remember it). Virtual destructors (dtors) are no different: start the destruction process `down' at the object's actual class, rather than `up' at the ptr's class (ie: `destroy yourself using the *correct* destruction routine'). Virtual destructors are so valuable that some people want compilers to holler at you if you forget them. In general there's only one reason *not* to make a class' dtor virtual: if that class has no virtual fns, the introduction of the first virtual fn imposes typically 4 bytes overhead in the size of each object (there's a bit of magic for how C++ `does the right thing', and it boils down to an extra ptr per object called the `virtual table pointer' or `vptr'). ============================================================================== Q76: What is a `virtual constructor'? A: Technically speaking, there is no such thing. You can get the effect you desire by a virtual `create_copy()' member fn (for copy constructing), or a `create_similar()' member fn (also virtual) which constructs/creates a new object of the same class but is `fresh' (like the `default' [zero parameter] ctor would do). The reason ctors can't be virtual is simple: a ctor turns raw bits into a living object. Until there's a living object against which to invoke the member function, you can't expect a member function invocation to be handled `the right way'. You can think of ctors as `class' [static] functions, or as `factories' which churn out objects. Thinking of ctors as `methods' attached to an object is misleading. Here is an example of how you could use `create_copy()' and `create_similar()' methods: class Set { //normally this would be a template public: virtual void insert(int); //Set of `int' virtual int remove(); //... virtual Set* create_copy() const = 0; //pure virtual; Set is an ABC virtual Set* create_similar() const = 0; virtual ~Set() { } //see on `virtual destructors' for more }; class SetHT : public Set { public: //... Set* create_copy() const { return new SetHT(*this); } Set* create_similar() const { return new SetHT(); } protected: //a hash table in here }; A SetHT is-a Set, so the return value is correct. The invocation of `SetHT(*this)' is that of copy construction (`*this' has type `const SetHT&'). Although `create_copy()' returns a new SetHT, the caller of create_copy() merely knows he has a Set, not a SetHT (which is desirable in the case of wanting a `virtual ctor'). `create_similar()' is similar, but it constructs an `empty' SetHT. Clients can use this as if they were `virtual constructors': void client_code(Set& s) { Set* s2 = s.create_copy(); Set* s3 = s.create_similar(); //... delete s2; //relies on destructor being virtual!! delete s3; // ditto } This fn will work correctly regardless of how the Set is implemented (hash table based, AVL tree based, etc). See above on `separation of interface from implementation' for more. -- Marshall Cline -- Marshall P. Cline, Ph.D. / Paradigm Shift Inc / PO Box 5108 / Potsdam NY 13676 cline@parashift.com / 315-353-6100 / FAX: 315-353-6110