liveBook · Manning

Public inheritance

As explained in 100 C++ Mistakes and How to Avoid Them

Public inheritance is C++ inheritance in which the public and protected members of a base class retain their corresponding accessibility in the derived class.

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

How 100 C++ Mistakes and How to Avoid Them covers it

How deep it goes
Explains is-a reasoning, invariants, substitution, object slicing, and interface design through contrasting C++ examples.
Examples
Uses Person–Student, Square–Horse, Rectangle–Square, Shape, and Moveable examples to contrast valid and invalid hierarchies.
What it assumes
Assumes basic C++ classes and pointers while teaching public inheritance, is-a reasoning, and substitution concerns.
What you can do after
Evaluate/understand whether a proposed hierarchy preserves the base contract and choose an abstract interface when it does not.
How it connects
Connects public inheritance with polymorphism, the Liskov substitution principle, class invariants, object slicing, composition, and interfaces.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 C++ Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.