Effective C++35: Make sure public inheritance models "isa"
Is penguin a bird? You may say yes, but this is not exactly the case in the realm of C++.
class Bird {
public:
virtual void fly(); // birds can fly
...
};
class Penguin:public Bird { // penguins are birds
...
};
We all know that bird can fly while penguins can't. But with this code, one surely can call Penguin::fly(). Now, tell me, is penguin a bird?
"Not all birds can fly", you may argue, "We are just the victims of an imprecise language."
Okay, let's take another example. Consider the following code:
class Rectangle {
public:
virtual void setHeight(int newHeight);
virtual void setWidth(int newWidth);
virtual int height() const; // return current
virtual int width() const; // values
...
};
void makeBigger(Rectangle& r) // function to
{ // increase r's area
int oldHeight = r.height();
r.setWidth(r.width() + 10); // add 10 to r's width
assert(r.height() == oldHeight); // assert that r's
} // height is unchanged
class Square: public Rectangle { ... };
Square s;
...
assert(s.width() == s.height()); // this must be true
// for all squares
makeBigger(s); // by inheritance, s
// isa Rectangle, so
// we can increase its
// area
assert(s.width() == s.height()); // this must still be
// true for all squares
Suqares are rectangles. And the width of a rectangle can be changed while the height stands unchanged. However, we can't perform the same operation on a square, thus deriving Square from Rectangle is not proper here.
A "isa" B means one thing in the real world, but another in the world of C++. In C++, A "isa" B means A can be used everwhere B can be used. And this is the most important thing of public inheritance.