C++ Inheritance Mechanisms and Implementation Strategies

Understanding Inheritance in C++

Inheritance serves as the most crucial mechanism in object-oriented programming for code reuse, allowing developers to extend existing classes while preserving their original characteristics. This approach creates new classes known as derived classes, establishing a hierarchical structure that mirrors the cognitive process of moving from simple to complex concepts. While function-level reuse is common, inheritance provides reuse at the class design level.

In any inheritance relationship, derived classes must exhibit characteristics that distinguish them from their base classes. Beyond inheriting base class members, derived classes typically extend their functionality by adding new data members and methods.

Basic Inheritance Syntax

Consider the following example where we define a base class and create derived classes from it:

class Individual {
public:
    void DisplayInfo() {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "default_name";
    int _age = 25;
};

class Learner : public Individual {
protected:
    int _studentID;
};

class Educator : public Individual {
protected:
    int _employeeID;
};

int main() {
    Learner l;
    Educator e;
    l.DisplayInfo();
    e.DisplayInfo();
    return 0;
}

Inheritance Types and Access Control

When using inheritance, access specifiers control how base class members are inherited:

  • private members of the base class are never directly accessible in derived classes, regardless of inheritance type
  • protected members were introduced specifically for inheritance, allowing access within derived classes
  • The access level of inherited members equals the minimum of the original access specifier and the inheritance type
  • When using class, default inheritance is private; when using struct, default inheritance is public

Public inheritance is the most commonly used form, as protected and private inheritance limit usability and reduce maintainability.

class Individual {
public:
    void Display() {
        cout << _name << endl;
    }
protected:
    string _name;
private:
    int _age;
};

class Learner : public Individual {
protected:
    int _learnerNumber;
};

Base and Derived Class Assignment

Derived class objects can be assigned to base class objects, pointers, or references—a process sometimes called slicing. However, base class objects cannot be assigned to derived class objects.

class Individual {
protected:
    string _name;
    string _gender;
    int _age;
};

class Learner : public Individual {
public:
    int _studentID;
};

void Test() {
    Learner learnerObj;
    // 1. Derived objects can be assigned to base objects/pointers/references
    Individual individualObj = learnerObj;
    Individual* ptr = &learnerObj;
    Individual& ref = learnerObj;
    
    // 2. Base objects cannot be assigned to derived objects
    // learnerObj = individualObj;  // This would cause an error
}

Inheritance and Scope

Base classes and derived classes have separate scopes. When both define members with the same name, the derived class member hides the base class member—a phenomenon known as hiding or redefinition. The base class member can still be accessed using the scope resolution operator.

class Individual {
protected:
    string _name = "John Doe";
    int _ID = 12345;
};

class Learner : public Individual {
public:
    void PrintInfo() {
        cout << "Name: " << _name << endl;
        // Explicitly access base class member
        cout << "ID: " << Individual::_ID << endl;
        // Default access to derived class member
        cout << "Student ID: " << _ID << endl;
    }
protected:
    int _ID = 67890; // Hides base class _ID
};

Default Member Functions in Derived Classes

When deriving classes, the six default member functions behave as follows:

  1. Constructors must call base class constructors to initialize base class members
  2. Copy constructors must call base class copy constructors
  3. Assignment operators must call base class assignment operators
  4. Destructors automatically call base class destructors after executing their own code
  5. Object initialization follows base class construction before derived class construction
  6. Object destruction follows derived class destruction before base class destruction
class Individual {
public:
    Individual(const char* name = "default")
        : _name(name) {
        cout << "Individual()" << endl;
    }
    
    Individual(const Individual& i)
        : _name(i._name) {
        cout << "Individual(const Individual&)" << endl;
    }
    
    Individual& operator=(const Individual& i) {
        cout << "Individual operator=" << endl;
        if (this != &i)
            _name = i._name;
        return *this;
    }
    
    ~Individual() {
        cout << "~Individual()" << endl;
    }
protected:
    string _name;
};

class Learner : public Individual {
public:
    Learner(const char* name, int id)
        : Individual(name)
        , _studentID(id) {
        cout << "Learner()" << endl;
    }
    
    Learner(const Learner& l)
        : Individual(l)
        , _studentID(l._studentID) {
        cout << "Learner(const Learner&)" << endl;
    }
    
    Learner& operator=(const Learner& l) {
        cout << "Learner operator=" << endl;
        if (this != &l) {
            Individual::operator=(l);
            _studentID = l._studentID;
        }
        return *this;
    }
    
    ~Learner() {
        cout << "~Learner()" << endl;
    }

protected:
    int _studentID;
};

Friendship and Inheritance

Friendship relationships are not inherited. A friend of a base class cannot access private or protected members of derived classes.

class Learner;
class Individual {
public:
    friend void Display(const Individual& i, const Learner& l);
protected:
    string _name;
};

class Learner : public Individual {
protected:
    int _studentNumber;
};

void Display(const Individual& i, const Learner& l) {
    cout << i._name << endl;
    // cout << l._studentNumber << endl;  // Not accessible
}

Static Members in Inheritance

When a base class defines static members, there is only one instance of that member throughout the entire inheritance hierarchy, regardless of how many derived classes exist.

class Individual {
public:
    Individual() { ++_count; }
protected:
    string _name;
public:
    static int _count; // Tracks number of individuals
};

int Individual::_count = 0;

class Learner : public Individual {
protected:
    int _studentID;
};

class Graduate : public Learner {
protected:
    string _researchTopic;
};

int main() {
    Learner l1, l2, l3;
    Graduate g1;
    cout << "Total individuals: " << Individual::_count << endl; // 4
    Learner::_count = 0;
    cout << "Total individuals: " << Individual::_count << endl; // 0
    return 0;
}

Diamond Inheritance and Virtual Inheritance

Single inheritance occurs when a derived class has only one direct base class. Multiple inheritance occurs when a derived class has two or more direct base classes. Diamond inheritance is a special case of multiple inheritance where two derived classes inherit from the same base class, and another class inherits from both derived classes.

Diamond inheritance introduces two main problems: data redundancy and ambiguity. Virtual inheritance can resolve these issues.

// Without virtual inheritance (problematic)
class Person {
public:
    string _name;
};

class Student : public Person {
protected:
    int _studentNumber;
};

class Teacher : public Person {
protected:
    int _teacherID;
};

class TeachingAssistant : public Student, public Teacher {
protected:
    string _majorSubject;
};

void Test() {
    TeachingAssistant ta;
    ta._name = "John";  // Ambiguity error
    // Must specify which base class:
    ta.Student::_name = "John";
    ta.Teacher::_name = "John";
}
// With virtual inheritance (solution)
class Person {
public:
    string _name;
};

class Student : virtual public Person {
protected:
    int _studentNumber;
};

class Teacher : virtual public Person {
protected:
    int _teacherID;
};

class TeachingAssistant : public Student, public Teacher {
protected:
    string _majorSubject;
};

void Test() {
    TeachingAssistant ta;
    ta._name = "John";  // No ambiguity
}

Inheritance vs. Composition

Public inheritance represents an "is-a" relationship, where each derived class object is also a base class object. Composition represents a "has-a" relationship, where each containing class object has another object as a member.

Composition is generally preferred over inheritance because it:

  • Results in lower coupling between classes
  • Better maintains encapsulation
  • Provides more flexible and maintainable code

However, inheritance is necessary for achieving polymorphism and is appropriate when an "is-a" relationship truly exists between classes.

// "is-a" relationship - inheritance
class Vehicle {
protected:
    string _color = "White";
    string _licensePlate = "ABC123";
};

class Motorcycle : public Vehicle {
public:
    void Ride() { cout << "Fast and agile" << endl; }
};

// "has-a" relationship - composition
class Wheel {
protected:
    string _brand = "Michelin";
    int _diameter = 17;
};

class Car {
protected:
    string _color = "White";
    string _licensePlate = "ABC123";
    Wheel _wheel;  // Composition
};

Tags: C++ Inheritance Object-Oriented Programming Polymorphism composition

Posted on Sat, 20 Jun 2026 17:26:12 +0000 by Mikersson