Public inheritance creates a derived class whose public and protected base-class interface remains available according to its original accessibility. Use it to express a genuine is-a relationship: code receiving a base-class pointer or reference must be able to substitute the derived object and obtain compatible behavior. Inheritance communicates meaning to readers as well as enabling polymorphism. section 7.9 section 7.7
Mistake 50 is using public inheritance merely to reuse implementation. A derived object must satisfy every relevant expectation established by its base. If changing it through the base interface can violate its invariant, the hierarchy is misleading, difficult to maintain, and potentially incorrect. section 7.9 section 7.7
The mutable Rectangle–Square trap
A Square publicly derived from Rectangle appears convenient because both have dimensions. However, code holding a Rectangle* can change height without changing width. That operation is valid for Rectangle but breaks the Square invariant, so Square cannot safely substitute for Rectangle. A safer hierarchy abstracts shared operations such as area, perimeter, and validation into Shape, then derives Rectangle and Square independently. section 7.9
Check a proposed public inheritance relationship before committing to it:
- Name the base-class behavior that callers may use, then verify that every derived object supports it in all cases. section 7.9
- Treat collections polymorphically through base pointers or references when derived behavior must remain available; storing objects by value can slice off the derived portion. section 7.7
- Keep code reuse secondary: composition or another design may be safer when the is-a relationship is absent. section 7.7
Listing 7.16 Improper inheritance that seems to save coding effort
class Rectangle {
private:
double height;
double width;
public:
Rectangle(double h, double w) : height(h), width(w) {}
double getHeight() { return height; }
void setHeight(double h) { height = h; }
double getWidth() { return width; }
void setWidth(double w) { width = w; }
virtual void validate() { assert(height >= 0 && width >= 0); }
};
class Square : public Rectangle { #1
public:
Square(double s) : Rectangle(s, s) {}
void validate() override {
Rectangle::validate();
assert(getHeight() == getWidth());
}
};
int main() {
std::vector<Rectangle*> shapes { new Rectangle(3, 4),
new Square(2) };
for (auto shape : shapes) {
// guarantee different lengths
shape->setHeight(shape->getWidth() + 1); #2
shape->validate();
}
return 0;
}
Inspect the annotated Rectangle–Square listing to see how a base-class mutation invalidates the derived invariant. section 7.9
