2 Inheritance

Inheritance is a fundamental concept in object-oriented programming that allows a class (called a derived class or subclass) to inherit properties and behaviors from another class (called a base class or superclass). The base class serves as a blueprint or template, defining common properties and behaviors shared by multiple derived classes. The derived class inherits these properties and behaviors from the base class, allowing it to extend and specialize the functionality as needed. This mechanism facilitates code reuse, promotes modularity, and supports the creation of hierarchical relationships between classes.

1 Base Class and Derived Class

  • The class that is being inherited from is called the base class or super class.
  • While the class that inherits from the base class is called the derived class or sub class.

The derived class inherits all non-private members (data members and member functions) of the base class.

2 Syntax of Inheritance

In C++, inheritance is declared using the class keyword followed by a colon (:) and the access specifier (public, protected, or private), followed by the name of the base class. The access specifier determines the accessibility of the inherited members in the derived class.

class Base {
public:
    int publicMember;
protected:
    int protectedMember;
private:
    int privateMember;
};

class Derived : accessSpecifier Base {
    // Members of Derived class
};

3 Access Control in Inheritance

  • Public Inheritance: In public inheritance, public members of the base class remain public in the derived class, protected members of the base class become protected in the derived class, and private members of the base class are not accessible in the derived class.
  • Protected Inheritance: In protected inheritance, all members of the base class become protected in the derived class.
  • Private Inheritance: In private inheritance, all members of the base class become private in the derived class.
class Base {
public:
    int publicMember;
protected:
    int protectedMember;
private:
    int privateMember;
};

class DerivedPublic : public Base {
    // publicMember is public
    // protectedMember is protected
    // privateMember is not accessible
};

class DerivedProtected : protected Base {
    // publicMember is protected
    // protectedMember is protected
    // privateMember is not accessible
};

class DerivedPrivate : private Base {
    // publicMember is private
    // protectedMember is private
    // privateMember is not accessible
};
Base Class Access SpecifierDerived Class Access SpecifierPublic MembersProtected MembersPrivate Members
PublicPublicPublicProtectedNot Accessible
PublicProtectedProtectedProtectedNot Accessible
PublicPrivatePrivatePrivateNot Accessible
ProtectedPublicProtectedProtectedNot Accessible
ProtectedProtectedProtectedProtectedNot Accessible
ProtectedPrivatePrivatePrivateNot Accessible
PrivatePublicNot AccessibleNot AccessibleNot Accessible
PrivateProtectedNot AccessibleNot AccessibleNot Accessible
PrivatePrivateNot AccessibleNot AccessibleNot Accessible

4 Constructors and Destructors in Inherited Classes

Derived classes can have their own constructors and destructors, which can initialize and clean up resources specific to the derived class. These constructors and destructors can call the corresponding constructors and destructors of the base class using the initialization lists and the :: scope resolution operator.

class Base {
public:
    Base() {
        // Base class constructor
    }
    ~Base() {
        // Base class destructor
    }
};

class Derived : public Base {
public:
    Derived() : Base() {
        // Derived class constructor
    }
    ~Derived() {
        // Derived class destructor
    }
};

5 Types of Inheritance

5.1 Single Inheritance

  • In single inheritance, a derived class inherits from only one base class.
  • It forms a simple hierarchy where each class has exactly one direct superclass.
  • Single inheritance is the most common type of inheritance in object-oriented programming.
class Base {
    // Base class definition
};

class Derived : public Base {
    // Derived class definition
};
    Base Class
        |
    Derived Class

5.2 Multiple Inheritance

  • In multiple inheritance, a derived class inherits from more than one base class.
  • It allows a class to inherit properties and behaviors from multiple unrelated classes.
  • Multiple inheritance can lead to the diamond problem, where ambiguity arises due to multiple inheritance paths.
class Base1 {
    // Base class definition
};

class Base2 {
    // Base class definition
};

class Derived : public Base1, public Base2 {
    // Derived class definition
};
    Base Class 1   Base Class 2
        \              /
         \            /
          Derived Class

5.3 Multilevel Inheritance

  • In multilevel inheritance, a derived class inherits from another derived class, forming a chain of inheritance.
  • It creates a hierarchy of classes with each level inheriting properties and behaviors from its parent class.
  • Multilevel inheritance allows for code reuse and promotes modularity.
class Base {
    // Base class definition
};

class Derived1 : public Base {
    // Derived class definition
};

class Derived2 : public Derived1 {
    // Derived class definition
};
    Base Class
        |
    Intermediate Derived Class
        |
    Derived Class

5.4 Hierarchical Inheritance

Multiple derived classes inherit from a single base class.

5.5 Hybrid Inheritance

  • Hybrid inheritance combines multiple inheritance and single inheritance.
  • It allows for the creation of complex class hierarchies by combining the features of both types of inheritance.
  • It may involve mix of single, multiple, multilevel.
  • Hybrid inheritance can lead to code complexity and should be used judiciously.
class Base1 {
    // Base class definition
};

class Base2 {
    // Base class definition
};

class Derived : public Base1, public Base2 {
    // Derived class definition
};

6 Diamond Problem

The "diamond problem" is a common issue that arises in programming languages that support multiple inheritance, such as C++. It occurs when a class inherits from two or more classes that have a common base class.

Let's illustrate the diamond problem with a graphical representation:

       A
      / \
     B   C
      \ /
       D

In this diagram, classes B and C both inherit from class A, and class D inherits from both B and C. This creates a diamond-shaped inheritance hierarchy, hence the name "diamond problem".

The diamond problem occurs when:

  • Both B and C define a method or member variable with the same name.
  • Class D tries to access this method or member variable without explicitly specifying which parent class it should inherit from.

For example, if both B and C define a method foo(), and D tries to call foo() without specifying whether it should use the implementation from B or C, the compiler cannot determine which version of foo() to use, resulting in ambiguity.

#include <iostream>

class Base {
public:
    void display() {
        std::cout << "Base class display()" << std::endl;
    }
};

class Derived1 : public Base {};

class Derived2 : public Base {};

class Derived3 : public Derived1, public Derived2 {};

int main() {
    Derived3 d;
    d.display(); // Compiler error: ambiguous call to 'display'
    return 0;
}

// Output
<source>: In function 'int main()':
<source>:18:7: error: request for member 'display' is ambiguous
   18 |     d.display(); // Compiler error: ambiguous call to 'display'
      |       ^~~~~~~
<source>:5:10: note: candidates are: 'void Base::display()'
    5 |     void display() {
      |          ^~~~~~~
<source>:5:10: note:                 'void Base::display()'

In the above example, Derived3 inherits from both Derived1 and Derived2, which in turn inherit from Base. When we try to call the display() function on an object of Derived3, the compiler encounters an ambiguity because Derived3 has two copies of the Base class due to multiple inheritance from Derived1 and Derived2.

Solution to the Diamond Problem:

There are several ways to resolve the diamond problem in C++.

1 Virtual Inheritance:

One solution is to use virtual inheritance, which ensures that only one instance of the common base class is shared among the derived classes. This prevents the duplication of base class members and resolves ambiguity.

We can make the base class virtually inherited by using the virtual keyword in the derived classes.

class Derived1 : virtual public Base {};

class Derived2 : virtual public Base {};

2 Override Ambiguous Functions:

  • Another solution is to override the ambiguous functions in the derived class to provide a clear definition of which version of the function should be used.
  • This approach requires explicitly defining the function in the derived class and providing a specific implementation.
class Derived3 : public Derived1, public Derived2 {
public:
    void display() {
        Derived1::display(); // Call display() from Derived1
    }
};