C++

Chapter 15: C++ Inheritance — Creating Class Hierarchies

By Ali Naqi • December 04, 2025

Chapter 15: C++ Inheritance — Creating Class Hierarchies

Chapter 15: Inheritance—Creating Class Hierarchies

You have mastered the creation of self-contained, robust objects using Encapsulation, Constructors, and Destructors. Now, we tackle the second foundational pillar of OOP: **Inheritance**.

Inheritance is the mechanism by which one class acquires the properties (data) and behavior (methods) of another class. It is used to express an **"is-a"** relationship between objects. For example, a Car **is a** Vehicle, and a Manager **is an** Employee.

Section 1: Base Classes and Derived Classes

In C++, inheritance establishes a parent-child relationship:

  • **Base Class (Parent/Superclass):** The class whose members are inherited. It contains the general, common features.
  • **Derived Class (Child/Subclass):** The class that inherits from the base class. It specializes the behavior and adds new, specific features.

Syntax of Inheritance

To inherit from a base class, you use the colon (:) followed by an **access specifier** and the name of the base class in the derived class definition.


// The general, common features
class Vehicle {
private:
    std::string serialNumber; // Private to Vehicle, not accessible by Car

protected:
    // Only accessible by Vehicle and its DERIVED classes (Car)
    int maxSpeed; 

public:
    // Accessible by everyone
    Vehicle(int speed) : maxSpeed(speed) {
        std::cout << "Vehicle base object created." << std::endl;
    }

    void startEngine() const {
        std::cout << "Vehicle engine starting." << std::endl;
    }
};

// The Car class inherits publicly from Vehicle
class Car : public Vehicle {
private:
    int numberOfDoors;

public:
    // Derived class constructor MUST call the Base class constructor
    Car(int speed, int doors) : Vehicle(speed), numberOfDoors(doors) {
        std::cout << "Car object created with " << numberOfDoors << " doors." << std::endl;
    }

    void drive() const {
        std::cout << "Car driving at max speed: " << maxSpeed << " km/h." << std::endl;
        // maxSpeed is accessible because it is 'protected' in the base class.
    }
};

Section 2: The Role of Access Specifiers in Inheritance

We have seen public and private. Now we introduce the third and most important specifier for inheritance: **protected**.

| Specifier | Accessible From: | Inside Base Class | Inside Derived Class | Outside Class (User Code) | | :--- | :--- | :--- | :--- | :--- | | **private** | Private to the class where it's defined. | Yes | No | No | | **protected** | Available to the class and its descendants. | Yes | Yes | No | | **public** | Available everywhere. | Yes | Yes | Yes |

**Summary:** If a data member needs to be hidden from external user code (main), but accessible to any specialized class that inherits from it, it should be declared protected.

Section 3: The Order of Construction and Destruction

When you create a Car object, two constructors are called: the Vehicle constructor and the Car constructor.

  1. **Construction Order:** **Base Class** constructor runs **first**, then the **Derived Class** constructor runs. (A house foundation is built before the roof.)
  2. **Destruction Order:** The reverse. **Derived Class** destructor runs **first**, then the **Base Class** destructor runs. (The specific resources of the car are cleaned up before the general vehicle resources.)

void demonstrate_inheritance() {
    Car myCar(200, 4); 
    // Output:
    // Vehicle base object created.
    // Car object created with 4 doors.
    
    myCar.startEngine(); // Inherited method
    myCar.drive();       // Specialized method
    
} // myCar goes out of scope, destructors called in reverse order
Mandatory Rule for Derived Class Constructors:
The derived class constructor **must** explicitly call the base class constructor in its initialization list, as seen with Car(int speed, int doors) : Vehicle(speed), .... If the base class has a non-default constructor (i.e., one that takes arguments), the compiler cannot automatically initialize the base part without your instruction.

Section 4: Method Overriding (Specialization)

A derived class can reuse an inherited method, or it can provide its own, specialized implementation. This is called **Method Overriding**.

In the following example, the base class Animal has a general makeSound(), but the derived class Dog provides a specific version.


class Animal {
public:
    void makeSound() const {
        std::cout << "Animal makes a sound." << std::endl;
    }
};

class Dog : public Animal {
public:
    // Overriding the base class method
    void makeSound() const {
        std::cout << "Dog barks: Woof! Woof!" << std::endl;
    }
    
    // Optional: Calling the base class method from the derived method
    void specificSound() const {
        Animal::makeSound(); // Calls the base implementation
        this->makeSound();     // Calls the derived implementation
    }
};

void demonstrate_overriding() {
    Dog d;
    d.makeSound();     // Output: Dog barks: Woof! Woof!
    d.specificSound(); // Output: Animal makes a sound. Dog barks: Woof! Woof!
}

Inheritance is a powerful tool for code reuse and structure, but it must be used carefully to maintain the "is-a" relationship.

Chapter 15 Conclusion and Next Steps

You now understand the fundamental concept of Inheritance, the role of the protected access specifier, and how base and derived classes interact during construction and method calling.

However, the simple overriding we saw in the last example has a critical limitation when we start working with pointers. If we have a pointer to an Animal that is holding a Dog object, C++ will currently call the wrong method. This leads us to the final, most advanced pillar of OOP.

In **Chapter 16**, we will introduce the concept of **Polymorphism** using **Virtual Functions**, resolving this pointer issue and unlocking the full power of object-oriented design in C++.