Effective C++ item 32: Make Sure Public Inheritance Models "is-a"

If you write that class D (“Derived”) publicly inherits from class B (“Base”), you are telling C++ compilers (as well as human readers of your code) that every object of type D is also an object of type B, but not vice versa. You are saying that B represents a more general concept than D, that D represents a more specialized concept than B. You are asserting that anywhere an object of type B can be used, an object of type D can be used just as well, because every object of type D is an object of type B. On the other hand, if you need an object of type D, an object of type B will not do: every D is-a B, but not vice versa.

There are many easy examples of such, like following

1
2
class Person {...};
class Student: public Person {...};

It’s more important to look at some examples which might seem to be easy but need more thoughts.
First example is bird and penguin. Penguin is-a bird, but not vice versa. So the natual way of writing penguin class is to publicly derive from bird class.

1
2
3
4
5
6
7
8
class Bird {
public:
virtual void fly();
...
};
class Penguin:public Bird {
...
};

You may realize something is not right. Penguin cannot fly! This shows us that concepts we use in our daily life is not very precise. When we say “birds can fly”, what we actually mean is “some birds can fly”. So to express this precisely, we should write something like following

1
2
3
4
5
6
7
8
9
10
11
class Bird {
...
};
class FlyingBird: public Bird {
public:
virtual void fly();
...
};
class Penguin: public Bird {
...
};

So don’t be fooled by our vague daily concepts or language.
Some people might say that I can still make Penguin publicly inherit from Bird with fly function but override it’s implementation with an exception or error msg. That is not a good solution since you just push the problem from compile-time to run-time, which is not a good thing.

Another example shows that instincts you’ve developed in other fields of study — including mathematics — may not serve you as well as you expect. Think about should class Square publicly inherit from class Rectangle? The answer looks obvious. Everybody knows that a square is a rectangle, but generally not vice versa. Wait until you see the code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Rectangle {
public:
virtual void setHeight(int newHeight);
virtual void setWidth(int newWidth);
virtual int height() const;
virtual int width() const;
...
};
void makeBigger(Rectangle& r) {
int oldHeight = r.height();
r.setWidth(r.width() + 10);
assert(r.height() == oldHeight);
}
class Square: public Rectangle {...};

int main() {
Square s;
...
assert(s.width() == s.height());
makeBigger(s);
assert(s.width() == s.height());
return 0;
}

assert(s.width() == s.height()); will trigger the assertion because makeBigger only modifies width. Square should always has the same width and height! The fundamental difficulty in this case is that something applicable to a rectangle (its width may be modified independently of its height) is not applicable to a square (its width and height must be the same). But public inheritance asserts that everything that applies to base class objects — everything! — also applies to derived class objects. In the case of rectangles and squares, that assertion fails to hold, so using public inheritance to model their relationship is simply incorrect.

Reference:
“Effective C++” Third Edition by Scott Meyers.