C++ Private Inheritance: Implemented-in-Terms-Of

In C++, private inheritance is a mechanism used to establish an "implemented-in-terms-of" relationship between classes, allowing a derived class to reuse the implementation details of a base class without exposing its interface to clients. While object composition is the standard and preferred design pattern for representing "implemented-in-terms-of" relationships, private inheritance provides a strictly syntactic alternative tailored for specialized scenarios. This article explains the mechanics of private inheritance, how it differs from public inheritance, and the specific technical conditions under which it is necessary.

The Mechanics of Private Inheritance

When a class inherits privately from a base class, the access levels of the base class members change within the derived class:

  • Public and protected members of the base class become private members of the derived class.
  • Private members of the base class remain inaccessible.
  • Implicit conversion from a derived class pointer or reference to a base class pointer or reference is disabled for the public; it is only permitted within member functions and friends of the derived class.

Because public access to the base class interface is severed, private inheritance does not model an "is-a" relationship (the Liskov Substitution Principle). A Derived object cannot be passed to a function expecting a Base object. Instead, the derived class merely uses the code of the base class to implement its own logic, completely hiding the base class identity from the outside world.

class Engine {
public:
    void start();
};

// Car is implemented in terms of Engine, not an Engine itself
class Car : private Engine {
public:
    void drive() {
        start(); // Permitted: derived class can access public members of base
    }
};

void run() {
    Car car;
    car.drive();  // OK
    // car.start(); // Compilation Error: 'start' is private in Car
}

Private Inheritance vs. Composition

Both composition (holding an instance of a class as a member variable) and private inheritance represent "implemented-in-terms-of." In idiomatic modern C++, the general rule is: prefer composition over inheritance, and use private inheritance only when strictly necessary.

Composition is cleaner, offers looser coupling, prevents namespace pollution, and allows runtime swapping of the internal implementation. However, composition cannot solve specific low-level object layout and language-access requirements.

When Private Inheritance Is Required

Private inheritance is utilized over composition in three primary technical scenarios:

1. Overriding Virtual Functions

If a class needs to use the functionality of a base class and must override its virtual functions, inheritance is mandatory. When you need to override these functions without exposing the base class's interface to external callers, private inheritance is the correct tool. This is common when using the Template Method pattern or working with event-handling frameworks.

class Timer {
public:
    virtual ~Timer() = default;
    void start();
private:
    virtual void onTick() = 0; // Hook to be overridden
};

class GameLoop : private Timer {
public:
    void run() {
        start();
    }
private:
    void onTick() override {
        // Custom implementation executed on each tick
    }
};

2. Accessing Protected Members

If class Derived needs access to the protected member variables or member functions of class Base, containment via composition will not work. A composed member cannot access protected members of its internal object. Private inheritance grants access to these protected members while preventing external consumers from accessing them.

3. Empty Base Optimization (EBO)

In C++, a standalone class with no non-static data members must still have a size of at least 1 byte to ensure distinct memory addresses for distinct objects. When an empty class is used as a member variable via composition, alignment requirements can increase the enclosing class's size by word boundaries (e.g., 4 to 8 bytes).

Through Empty Base Optimization (EBO), a derived class that inherits from an empty base class pays zero size overhead for that base. Standard library components such as std::vector and smart pointers historically used private inheritance to hold stateless allocators and deleters with zero space cost. Note that C++20 introduced the [[no_unique_address]] attribute, which largely eliminates the need for private inheritance to achieve EBO in modern codebases.