


Never, never, never do this. Never. Never!
Attempting to hide (eliminate, revoke, privatize) inherited public member functions is an all-too-common design error. It usually stems from muddy thinking.
(Note: this FAQ has to do with public inheritance; private and protected inheritance are different.)
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
C++ allows a Derived* to be converted to a Base*, since a Derived object is a kind of a Base object. However trying to convert a Derived** to a Base** is flagged as an error. Although this error may not be obvious, it is nonetheless a good thing. For example, if you could convert a Car** to a Vehicle**, and if you could similarly convert a NuclearSubmarine** to a Vehicle**, you could assign those two pointers and end up making a Car* point at a NuclearSubmarine:
In other words, if it was legal to convert a Derived** to a Base**, the Base** could be dereferenced (yielding a Base*), and the Base* could be made to point to an object of a different derived class, which could cause serious problems for national security (who knows what would happen if you invoked the openGasCap() member function on what you thought was a Car, but in reality it was a NuclearSubmarine!! Try the above code out and see what it does on most compilers it will call NuclearSubmarine::fireNuclearMissle()!
(BTW you'll need to use a pointer cast to get it to compile. Suggestion: try to compile it without a pointer cast to see what the compiler does. If you're really quiet when the error message appears on the screen, you should be able to hear the muffled voice of your compiler pleading with you, "Please don't use a pointer cast! Pointer casts prevent me from telling you about errors in your code, but they don't make your errors go away! Pointer casts are evil!" At least that's what my compiler says.)
(Note: this FAQ has to do with public inheritance; private and protected inheritance are different.)
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]

Nope.
I know it sounds strange, but it's true. You can think of this as a direct consequence of the previous FAQ, or you can reason it this way: if the kind-of relationship were valid, then someone could point a parking-lot-of-Vehicle pointer at a parking-lot-of-Car, which would allow someone to add any kind of Vehicle to a parking-lot-of-Car (assuming parking-lot-of-Vehicle has a member function like add(Vehicle&)). In other words, you could park a Bicycle, SpaceShuttle, or even a NuclearSubmarine in a parking-lot-of-Car. Certainly it would be surprising if someone accessed what they thought was a Car from the parking-lot-of-Car, only to find that it is actually a NuclearSubmarine. Gee, I wonder what the ejectCassetteTape() method would do??
Perhaps this will help: a container of Thing is not a kind-of container of Anything even if a Thing is a kind-of an Anything. Swallow hard; it's true.
You don't have to like it. But you do have to accept it.
One last example which we use in our OO/C++ training courses: "A Bag-of-Apple is not a kind-of Bag-of-Fruit." If a Bag-of-Apple could be passed as a Bag-of-Fruit, someone could put a Banana into the Bag, even though it is supposed to only contain Apples!
(Note: this FAQ has to do with public inheritance; private and protected inheritance are different.)
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Nope.
This is a corollary of the previous FAQ. Unfortunately this one can get you into a lot of hot water. Consider this:
The compiler thinks this is perfectly type-safe. Line 5 converts a Derived* to a Base*. But in reality it is horrendously evil: since Derived is larger than Base, the pointer arithmetic done on line 3 is incorrect: the compiler uses sizeof(Base) when computing the address for arrayOfBase[1], yet the array is an array of Derived, which means the address computed on line 3 (and the subsequent invocation of member function f()) isn't even at the beginning of any object! It's smack in the middle of a Derived object. Assuming your compiler uses the usual approach to virtual functions, this will reinterpret the int i_ of the first Derived as if it pointed to a virtual table, it will follow that "pointer" (which at this point means we're digging stuff out of a random memory location), and grab one of the first few words of memory at that location and interpret them as if they were the address of a C++ member function, then load that (random memory location) into the instruction pointer and begin grabbing machine instructions from that memory location. The chances of this crashing are very high.
The root problem is that C++ can't distinguish between a pointer-to-a-thing and a pointer-to-an-array-of-things. Naturally C++ "inherited" this feature from C.
NOTE: If we had used an array-like class (e.g., std::vector<Derived> from the standard library) instead of using a raw array, this problem would have been properly trapped as an error at compile time rather than a run-time disaster.
(Note: this FAQ has to do with public inheritance; private and protected inheritance are different.)
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Yes, arrays are evil. (only half kidding).
Seriously, arrays are very closely related to pointers, and pointers are notoriously difficult to deal with. But if you have a complete grasp of why the above few FAQs were a problem from a design perspective (e.g., if you really know why a container of Thing is not a kind-of container of Anything), and if you think everyone else who will be maintaining your code also has a full grasp on these OO design truths, then you should feel free to use arrays. But if you're like most people, you should use a template container class such as std::vector<T> from the standard library rather than raw arrays.
(Note: this FAQ has to do with public inheritance; private and protected inheritance are different.)
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Depends. But not if Ellipse guarantees it can change its size asymmetrically.
For example, if Ellipse has a setSize(x,y) member function that promises the object's width() will be x and its height() will be y, Circle can't be a kind-of Ellipse. Simply put, if Ellipse can do something Circle can't, then Circle can't be a kind of Ellipse.
This leaves two potential (valid) relationships between Circle and Ellipse:
In the first case, Ellipse could be derived from class AsymmetricShape, and setSize(x,y) could be introduced in AsymmetricShape. However Circle could be derived from SymmetricShape which has a setSize(size) member function.
In the second case, class Oval could only have setSize(size) which sets both the width() and the height() to size. Ellipse and Circle could both inherit from Oval. Ellipse but not Circle could add the setSize(x,y) operation (but beware of the hiding rule if the same member function name setSize() is used for both operations).
(Note: this FAQ has to do with public inheritance; private and protected inheritance are different.)
(Note: setSize(x,y) isn't sacred. Depending on your goals, it may be okay to prevent users from changing the dimensions of an Ellipse, in which case it would be a valid design choice to not have a setSize(x,y) method in Ellipse. However this series of FAQs discusses what to do when you want to create a derived class of a pre-existing base class that has an "unacceptable" method in it. Of course the ideal situation is to discover this problem when the base class doesn't yet exist. But life isn't always ideal...)
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
If you claim that all Ellipses can be squashed asymmetrically, and you claim that Circle is a kind-of Ellipse, and you claim that Circle can't be squashed asymmetrically, clearly you've got to adjust (revoke, actually) one of your claims. You can get rid of Ellipse::setSize(x,y), get rid of the inheritance relationship between Circle and Ellipse, or admit that your Circles aren't necessarily circular. (You can also get rid of Circle completely, where circleness is just a temporary state of an Ellipse object rather than a permanent quality of the object.)
Here are the two most common traps new OO/C++ programmers regularly fall into. They attempt to use coding hacks to cover up a broken design (they redefine Circle::setSize(x,y) to throw an exception, call abort(), choose the average of the two parameters, or to be a no-op). Unfortunately all these hacks will surprise users, since users are expecting width() == x and height() == y. The one thing you must not do is surprise your users.
If it is important to you to retain the "Circle is a kind-of Ellipse" inheritance relationship, you can weaken the promise made by Ellipse's setSize(x,y). E.g., you could change the promise to, "This member function might set width() to x and/or it might set height() to y, or it might do nothing". Unfortunately this dilutes the contract into dribble, since the user can't rely on any meaningful behavior. The whole hierarchy therefore 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).
(Note: this FAQ has to do with public inheritance; private and protected inheritance are different.)
(Note: setSize(x,y) isn't sacred. Depending on your goals, it may be okay to prevent users from changing the dimensions of an Ellipse, in which case it would be a valid design choice to not have a setSize(x,y) method in Ellipse. However this series of FAQs discusses what to do when you want to create a derived class of a pre-existing base class that has an "unacceptable" method in it. Of course the ideal situation is to discover this problem when the base class doesn't yet exist. But life isn't always ideal...)
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]

Actually, it doesn't mean any of these things. But I'll tell you what it does mean (you may not like what I'm about to say): it means your intuitive notion of "kind of" is leading you to make bad inheritance decisions. Your tummy is lying to you about what good inheritance really means stop believing those lies.
Look, I have received and answered dozens of passionate e-mail messages about this subject. I have taught it hundreds of times to thousands of software professionals all over the place. I know it goes against your intuition. But trust me; your intuition is wrong (where "wrong" means "will cause you to make bad decisions in OO design and programming").
Here's how to make good decisions in OO design and programming (at least those decisions that have to do with inheritance): recognize that the derived class objects must be substitutable for the base class objects. That means objects of the derived class must behave in a manner consistent with the promises made in the base class' contract. Once you believe this (and I fully recognize that you might not yet, but you will if you work at it with an open mind), you'll see that setSize(x,y) violates this substitutability.
There are three ways to fix this problem:
Sorry, but there simply are no other choices.
Another way to say this is that you have to either make the base class weaker (in this case weaken Ellipse to the point that it no longer guarantees you can set its width and height to different values), make the derived class stronger (in this case empower a Circle with the ability to be both symmetric and, ahem, asymmetric), or admit that a Circle is not substitutable for Ellipse.
Important: there really are no other choices than the above three. In particular:
(Note: this FAQ has to do with public inheritance; private and protected inheritance are different.)
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
If Circle is the base class and Ellipse is the derived class, then you run into a whole new set of problems. For example, suppose Circle has a radius() method. Then Ellipse will also need to have a radius() method, but that doesn't make much sense: what does it even mean for a (possibly assymetric) ellipse to have a radius?
If you get over that hurdle (e.g., by having Ellipse::radius() return the average of the major and minor axes, or whatever), then there is a problem with the relationship between radius() and area(). E.g., suppose Circle has an area() method that promises to return 3.14159[etc] times the square whatever radius() returns. Then either Ellipse::area() will not return the true area of the ellipse, or you'll have to stand on your head to get radius() to return something that matches the above formula.
Even if you get past that one (i.e., by having Ellipse::radius() return the square root of the ellipse's area divided by pi), you'll get stuck by the circumference() method. E.g., suppose Circle has a circumference() method that promises to return two times pi times whatever is returned by radius(). Now you're stuck: there's no way to make all those constraints work out for Ellipse: the Ellipse class will have to lie about its area, its circumference, or both.
Bottom line: you can make anything inherit from anything provided the methods in the derived class abide by the promises made in the base class. But you ought not to use inheritance just because you feel like it, or just because you want to get code reuse. You should use inheritance (a) only if the derived class's methods can abide by all the promises made in the base class, and (b) only if you don't think you'll confuse your users, and (c) only if there's something to be gained by using the inheritance some real, measurable improvement in time, money or risk.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Ahhh, there's the rub. You think the Circle/Ellipse example is just a silly example. But in reality, your problem is an isomorphism to that example.
I don't care what your inheritance problem is, but all (yes all) bad inheritances boil down to the Circle-is-not-a-kind-of-Ellipse example.
Here's why: Bad inheritances always have a base class with an extra capability (often an extra member function or two; sometimes an extra promise made by one or a combination of member functions) that a derived class can't satisfy. You've either got to make the base class weaker, make the derived class stronger, or eliminate the proposed inheritance relationship. I've seen lots and lots and lots of these bad inheritance proposals, and believe me, they all boil down to the Circle/Ellipse example.
Therefore, if you truly understand the Circle/Ellipse example, you'll be able to recognize bad inheritance everywhere. If you don't understand what's going on with the Circle/Ellipse problem, the chances are high that you'll make some very serious and very expensive inheritance mistakes.
Sad but true.
(Note: this FAQ has to do with public inheritance; private and protected inheritance are different.)
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]

Probably not.
The most important insight is that the answer depends on the details of the base class's contract. It is not enough to know that the public interfaces / method signatures are compatible; one also needs to know if the contracts / behaviors are compatible.
The important part of the previous sentence are the words "contracts / behaviors." That phrase goes well beyond the public interface = method signatures = method names and parameter types and constness. A method's contract means its advertised behavior = advertised requirements and promises = advertised preconditions and postconditions. So if the base class has a method void insert(const Foo& x), the contract of that method includes the signature (meaning the name insert and the parameter const Foo&), but goes well beyond that to include the method's advertised preconditions and postconditions.
The other important word is advertised. The intention here is to differentiate between the code inside the method (assuming the base class's method has code; i.e., assuming it's not an unimplemented pure virtual function) and the promises made outside the method. This is where things get tricky. Suppose List::insert(const Foo& x) inserts a copy of x at the end of this List, and the override of that method in SortedList inserts x in the proper sort-order. Even though the override behaves in a way that is incompatible with the base class's code, the inheritance might still be proper if the base class makes a "weak" or "adaptable" promise. For example, if the advertised promise of List::insert(const Foo& x) is something vague like, "Promises a copy of x will be inserted somewhere within this List," then the inheritance is probably okay since the override abides by the advertised behavior even though it is incompatible with the implemented behavior.
The derived class must do what the base class promises, not what it actually does.
The key is that we've separated the advertised behavior ("specification") from implemented behavior ("implementation"), and we rely on the specification rather than the implementation. This is very important because in a large percentage of the cases the base class's method is an unimplemented pure virtual the only thing that can be relied on is the specification there simply is no implementation on which to rely.
Back to SortedList and List: it seems likely that List has one or more methods that have contracts which guarantee order, and therefore SortedList is probably not a kind-of List. For example, if List has a method that lets you reorder things, prepend things, append things, or change the ith element, and if those methods make the typical advertised promise, then SortedList would need to violate that advertised behavior and the inheritance would be improper. But it all depends on what the base class advertises on the base class's contract.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
E-mail the author
[ C++ FAQ Lite
| Spis treści
| Skorowidz
| O autorze
| ©
| Pobierz swoją własną kopię ]
Ostatnia aktualizacja Jun 17, 2002
Wersja
polska: 0.1h Oct 3, 2003