C++ for C Developers | Lesson 8: Inheritance: Building on What You Have (Code Reuse)

Goal: Understand how inheritance allows one class (derived) to acquire the properties and methods of another class (base), promoting code reuse and establishing hierarchical relationships (“is-a”). Learn how access specifiers (public, protected, private) work in inheritance.

1. Motivation: Code Reuse and Relationships

Imagine you have different types of vehicles: Car, Truck, Motorcycle. They likely share common attributes (speed, weight, license plate) and behaviors (start engine, stop engine, accelerate). Instead of copying and pasting this common code into each vehicle class, inheritance lets us define a general Vehicle class and have Car, Truck, etc., inherit from it.

flowchart TD
    Animal["Animal (Base Class)"]
    Dog["Dog (Derived Class)"]
    Cat["Cat (Derived Class)"]
    Animal -->|public inheritance| Dog
    Animal -->|public inheritance| Cat

Inheritance models an “is-a” relationship: a Car is a type of Vehicle, a Dog is a type of Animal.

2. Basic Syntax

You define a derived class using a colon : followed by an access specifier and the base class name.

C++
class Base {
    // ... base class members ...
};

class Derived : public Base { // Derived inherits publicly from Base
    // ... members specific to Derived ...
};
  • Base Class: The class being inherited from (Base).
  • Derived Class: The class doing the inheriting (Derived).
  • public Inheritance: This is the most common type. It means “Derived is publicly a type of Base”. We’ll focus mainly on this. (protected and private inheritance exist but are less common and used for different relationship types).

3. How Access Specifiers Work with Inheritance (public inheritance)

When Derived inherits publicly from Base:

  • public members of Base become public members of Derived. (Accessible from anywhere).
  • protected members of Base become protected members of Derived. (Accessible within Derived and its own future derived classes, but not outside).
  • private members of Base remain private to Base. They are part of the Derived object’s memory, but they cannot be directly accessed by name within Derived‘s methods. Derived can only interact with them via public or protected methods inherited from Base.

Access Summary Table (public Inheritance):

Base Member AccessAccessible in Base Methods?Accessible in Derived Methods?Accessible Outside?
publicYesYesYes
protectedYesYesNo
privateYesNoNo

4. Example: Animals

Let’s model some animals.

C++
#include <iostream>
#include <string>

// --- Base Class ---
class Animal {
private: // Private to Animal
    std::string m_internal_id; // Only accessible via Animal's methods

protected: // Accessible by Animal and derived classes (like Dog)
    std::string m_species;
    int m_age;

public: // Accessible by anyone
    // Constructor for Animal
    Animal(const std::string& species, int age)
        : m_species(species), m_age(age)
    {
        // Generate some internal ID (example)
        m_internal_id = species + std::to_string(age); // std::to_string needs <string>
        std::cout << "Animal Constructor for " << m_species << std::endl;
    }

    // Destructor for Animal
    ~Animal() {
         std::cout << "Animal Destructor for " << m_species << std::endl;
    }

    // Public methods
    void eat() const {
        std::cout << "The " << m_species << " is eating." << std::endl;
    }

    void sleep() const {
        std::cout << "The " << m_species << " is sleeping." << std::endl;
    }

    int getAge() const {
        return m_age; // Provide public access to protected data if needed
    }

    std::string getSpecies() const {
        return m_species;
    }

    std::string getInternalIdInfo() const {
        // Derived classes cannot access m_internal_id directly,
        // but they can call this public method.
        return "Internal ID starts with: " + m_internal_id.substr(0, 4) + "...";
    }
};

// --- Derived Class ---
class Dog : public Animal { // Dog "is-a" Animal (publicly inherits)
private: // Specific to Dog
    std::string m_breed;
    std::string m_owner_name;

public:
    // Dog's Constructor - MUST call the Base class (Animal) constructor
    Dog(const std::string& breed, int age, const std::string& owner)
        : Animal("Dog", age), // *** Call Animal constructor via initialization list ***
          m_breed(breed),
          m_owner_name(owner)
    {
        std::cout << "Dog Constructor for breed " << m_breed << std::endl;
    }

    ~Dog() {
         std::cout << "Dog Destructor for breed " << m_breed << std::endl;
    }

    // Dog-specific method
    void bark() const {
        // Can access protected members from Animal directly
        std::cout << "The " << m_age << "-year-old " << m_species << " (breed: " << m_breed << ") says: Woof! Woof!" << std::endl;
        // std::cout << m_internal_id; // ERROR! Cannot access private base member
    }

    // Can call public methods inherited from Animal
    void fetch() const {
        std::cout << m_owner_name << "'s dog (" << m_species << ") is fetching." << std::endl;
        eat(); // Call inherited public method
    }

    // We can provide a Dog-specific version of an Animal method (Overriding preview)
    void sleep() const {
        std::cout << "The dog (" << m_breed << ") curls up to sleep." << std::endl;
        // Can still call the base version if needed:
        // Animal::sleep();
    }

     void showInternalIdHint() const {
        // Can access Base's private data *indirectly* via Base's public method
        std::cout << getInternalIdInfo() << std::endl;
     }
};


int main() {
    std::cout << "--- Creating Dog object ---" << std::endl;
    Dog myDog("Golden Retriever", 5, "Alice");
    std::cout << "-------------------------\n" << std::endl;

    // Call methods:
    myDog.eat();          // Inherited from Animal
    myDog.bark();         // Defined in Dog
    myDog.fetch();        // Defined in Dog (calls inherited eat())
    myDog.sleep();        // Uses the Dog version (overrides Animal's)
    myDog.showInternalIdHint(); // Calls inherited method to access private base data hint

    std::cout << "\nMy dog's species: " << myDog.getSpecies() << std::endl; // Inherited public method
    std::cout << "My dog's age: " << myDog.getAge() << std::endl;      // Inherited public method

    // Cannot access protected members from outside:
    // std::cout << myDog.m_species; // ERROR! m_species is protected

    // Cannot access private members from outside:
    // std::cout << myDog.m_breed; // ERROR! m_breed is private to Dog
    // std::cout << myDog.m_internal_id; // ERROR! m_internal_id is private to Animal


    std::cout << "\n--- Dog object going out of scope ---" << std::endl;
    // Destructors called in reverse order of construction:
    // 1. Dog destructor
    // 2. Animal destructor
    return 0;
}

Key Observations from Example:

  • Constructor Chaining: The Dog constructor must initialize its Animal part. This is done in the initialization list by calling the Animal constructor (Animal("Dog", age)). If the base class had a default constructor, it would be called automatically if not specified, but it’s good practice to be explicit.
  • Access Control: Dog methods can access m_species and m_age (protected in Animal), but not m_internal_id (private in Animal). Outside code (main) cannot access protected or private members directly.
  • Code Reuse: Dog automatically gets eat(), getAge(), getSpecies(), etc., without redefining them.
  • Specialization: Dog adds its own data (m_breed, m_owner_name) and methods (bark(), Workspace()).
  • Overriding (Preview): Dog provided its own sleep() method. The Dog version is called for Dog objects. (More on this with polymorphism next time).
  • Destructor Chaining: When myDog is destroyed, the Dog destructor runs first, followed automatically by the Animal destructor.
sequenceDiagram
    participant Main
    participant Dog
    participant Animal

    Main->>Dog: Create Dog object
    Dog->>Animal: Call Animal constructor
    Animal-->>Dog: Animal initialized
    Dog-->>Main: Dog initialized

    Main->>Main: Use Dog object

    Main->>Dog: Dog object goes out of scope
    Dog->>Dog: Dog destructor runs
    Dog->>Animal: Animal destructor runs

Why protected?

Use protected for members that are implementation details of the base class but which derived classes might reasonably need direct access to for their own implementation, without exposing them publicly. Use private for things truly internal to the base class only. Start with private and change to protected only if necessary for derived classes.

flowchart LR
    Base("Base Class")
    Derived("Derived Class")
    Outside("Outside Code")
    subgraph Members in Base
        P1("public m1()")
        P2("protected m2()")
        P3("private m3()")
    end
    Base --> P1
    Base --> P2
    Base --> P3
    Derived -->|Can Access| P1
    Derived -->|Can Access| P2
    Derived -.->|Cannot Access| P3
    Outside -->|Can Access| P1
    Outside -.->|Cannot Access| P2
    Outside -.->|Cannot Access| P3

Summary of Lesson 8:

  • Inheritance allows a Derived class to inherit members from a Base class using class Derived : public Base.
  • It models “is-a” relationships and promotes code reuse.
  • public Inheritance Rules:
    • public base members -> public in derived.
    • protected base members -> protected in derived.
    • private base members -> Inaccessible by name in derived.
  • protected allows access within the class and derived classes, but not outside.
  • Derived class constructors must call the appropriate base class constructor in their initialization list.
  • Destructors are called automatically in reverse order of construction (Derived then Base).

Next Lesson: We’ll unlock the true power of inheritance with Polymorphism and Virtual Functions, allowing you to treat objects of derived classes through base class pointers/references and have the correct, specialized methods called automatically at runtime.

More Sources:

cppreference – Derived Classes: https://en.cppreference.com/w/cpp/language/derived_class

Learn C++ – Inheritance Tutorial: https://www.learncpp.com/cpp-tutorial/basic-inheritance-in-c/

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top