Inheritance in C++

Table of Contents

Inheritance in C++

Inheritance in C++ is an object-oriented programming (OOP) concept that allows one class (called the derived class) to inherit the properties and behaviors (data members and member functions) from another class (called the base class). Inheritance helps to promote code reusability, allowing the derived class to use or modify the features of the base class without having to rewrite the code.

Key Points of Inheritance:

  1. Base Class: The class whose properties and behaviors are inherited by another class.
  2. Derived Class: The class that inherits the properties and behaviors from another class.
  3. Access Control: The derived class can access public and protected members of the base class, but private members of the base class are not directly accessible to the derived class.
  4. Code Reusability: Inheritance promotes the reuse of code, as the derived class does not need to redefine the base class’s functionality.

 

Types of Inheritance in C++

There are several types of inheritance in C++, each with different purposes and relationships between the base and derived classes:

  • Single Inheritance
  • Multiple Inheritance
  • Multilevel Inheritance
  • Hierarchical Inheritance
  • Hybrid Inheritance

 

1. Single Inheritance

Single Inheritance in C++ refers to a type of inheritance where a class (called the derived class) inherits properties and behaviors (data members and functions) from a single base class. This means the derived class can access and extend the functionality of the base class, but it only has one immediate parent class.

Key Points of Single Inheritance:

  1. Single Parent Class: The derived class has only one base class from which it inherits members.
  2. Code Reusability: The derived class can reuse the code (methods and properties) of the base class.
  3. Simplicity: Single inheritance is relatively simple compared to multiple inheritance, as it avoids complexities such as ambiguity between multiple base classes.

 

Example of Single Inheritance:

#include <iostream>

using namespace std;

// Base class

class Animal

{

public:

void eat()

{

cout << “This animal eats food.” << endl;

}

};

// Derived class inheriting from Animal class

class Dog : public Animal

{

public:

void bark()

{

cout << “The dog barks.” << endl;

}

};

int main()

{

Dog myDog;     // Calling function from base class

myDog.eat();   // Inherited function

// Calling function from derived class

myDog.bark();  // Derived function

return 0;

}

Output:

This animal eats food.

The dog barks.

 

In the example above:

  • Dog is the derived class, and it inherits the eat() function from the Animal base class.
  • The derived class Dog also has its own member function, bark().

In single inheritance, the derived class can access public and protected members of the base class, but private members are not directly accessible. The derived class may also override base class methods if needed.

 

2. Multiple Inheritance

Multiple Inheritance in C++ is a type of inheritance where a class (called the derived class) inherits from more than one base class. This allows the derived class to acquire the properties and behaviors (data members and functions) from multiple base classes, enabling it to combine features of all the base classes.

Key Points of Multiple Inheritance:

  1. Multiple Base Classes: A derived class can inherit from two or more base classes.
  2. Code Reusability: The derived class can reuse functionalities from multiple base classes.
  3. Flexibility: It allows the creation of more complex and feature-rich derived classes by combining the characteristics of multiple classes.
  4. Potential Ambiguity: Multiple inheritance can sometimes lead to ambiguity, especially if the base classes have members with the same name (e.g., functions or variables). C++ provides mechanisms like scope resolution operators to handle such cases.

 

Example of Multiple Inheritance:

#include <iostream>

using namespace std;

// Base class 1

class Animal

{

public:

void eat()

{

cout << “This animal eats food.” << endl;

}

};

// Base class 2

class Bird

{

public:

void fly()

{

cout << “This bird flies.” << endl;

}

};

// Derived class inheriting from both Animal and Bird

class Bat : public Animal, public Bird

{

public:

void sleep()

{

cout << “The bat sleeps during the day.” << endl;

}

};

int main()

{

Bat myBat;     // Calling function from Animal class

myBat.eat();   // From Animal class

// Calling function from Bird class

myBat.fly();   // From Bird class

// Calling function from Bat class

myBat.sleep(); // From Bat class

return 0;

}

Output:

This animal eats food.

This bird flies.

The bat sleeps during the day.

 

In this example:

  • The Bat class is derived from two base classes, Animal and Bird.
  • The Bat class inherits the eat() function from Animal and the fly() function from Bird, in addition to having its own sleep() function.

Advantages of Multiple Inheritance:

  • Combines Features: You can create classes that inherit characteristics from more than one base class, thus combining their features.
  • Code Reusability: You can reuse code from multiple classes, avoiding duplication.

Disadvantages of Multiple Inheritance:

  • Ambiguity: If two or more base classes have functions or members with the same name, it can lead to ambiguity, making it unclear which function or member the derived class should use.

For example:

class ClassA

{

public:

void show()

{

cout << “ClassA show()” << endl;

}

};

class ClassB

{

public:

void show()

{

cout << “ClassB show()” << endl;

}

};

class Derived : public ClassA, public ClassB

{

public:

// Which show() method will be called?

};

This requires explicit resolution using scope resolution (ClassA::show(), ClassB::show()).

  • Complexity: Multiple inheritance can make the code more complex and harder to maintain, especially when dealing with large hierarchies.

Multiple inheritance is a powerful feature of C++, but it should be used with care to avoid confusion and complexity, especially when dealing with ambiguities.

 

3. Multilevel Inheritance

Multilevel Inheritance in C++ is a type of inheritance where a class is derived from another class, which itself is derived from another class, forming a chain of inheritance. In other words, the derived class inherits from a base class, and then another class inherits from this derived class, continuing the inheritance hierarchy.

Key Points of Multilevel Inheritance:

  1. Chaining of Classes: Multilevel inheritance creates a chain of inheritance, where one class is derived from another, and so on.
  2. Code Reusability: Each class in the chain can reuse functionality from the class above it, promoting code reusability.
  3. Inheritance Hierarchy: It establishes a clear inheritance hierarchy, where the base class is at the top, and the derived classes inherit from each other.

 

Example of Multilevel Inheritance:

#include <iostream>

using namespace std;

// Base class

class Animal

{

public:

void eat()

{

cout << “This animal eats food.” << endl;

}

};

// Derived class 1 (inherits from Animal)

class Mammal : public Animal

{

public:

void breathe()

{

cout << “This mammal breathes air.” << endl;

}

};

// Derived class 2 (inherits from Mammal)

class Dog : public Mammal

{

public:

void bark()

{

cout << “This dog barks.” << endl;

}

};

int main()

{

Dog myDog;

// Calling functions from the entire inheritance chain

myDog.eat();    // From Animal class

myDog.breathe(); // From Mammal class

myDog.bark();    // From Dog class

return 0;

}

Output:

This animal eats food.

This mammal breathes air.

This dog barks.

 

In this example:

  • Dog is the derived class that inherits from Mammal.
  • Mammal is the intermediate class that inherits from Animal.
  • The Dog class has access to all the methods from both Mammal (like breathe()) and Animal (like eat()), in addition to its own methods (like bark()).

Advantages of Multilevel Inheritance:

  • Code Reusability: Each class can reuse code from its parent class and avoid redundant code.
  • Extensibility: The behavior of the base class can be extended and modified in each derived class.
  • Logical Hierarchy: It creates a natural, logical hierarchy, which is useful in organizing classes that share a common feature.

Disadvantages of Multilevel Inheritance:

  • Complexity: As the hierarchy becomes deeper, it may increase the complexity of the code, making it harder to maintain and understand.
  • Tight Coupling: Each derived class is tightly coupled to its parent classes. If changes are made in a base class, they could affect all derived classes in the hierarchy.

Multilevel inheritance is useful when you want to build a class hierarchy where each subclass builds upon the functionality of the base class in a logical and structured manner. However, just like with other forms of inheritance, it should be used judiciously to avoid unnecessary complexity.

 

4. Hierarchical Inheritance

Hierarchical Inheritance in C++ is a type of inheritance where multiple derived classes inherit from a single base class. In other words, a single base class serves as the parent for more than one derived class. All derived classes share the common functionality of the base class, but each derived class can also have its own unique functionality.

Key Points of Hierarchical Inheritance:

  1. Single Base Class: There is only one base class, from which multiple derived classes inherit.
  2. Code Reusability: All derived classes can reuse the properties and methods of the common base class.
  3. Independent Derived Classes: Each derived class can implement additional functionalities specific to itself, while still retaining the functionality of the base class.

 

Example of Hierarchical Inheritance:

#include <iostream>

using namespace std;

// Base class

class Animal

{

public:

void eat()

{

cout << “This animal eats food.” << endl;

}

};

// Derived class 1

class Dog : public Animal

{

public:

void bark()

{

cout << “This dog barks.” << endl;

}

};

// Derived class 2

class Cat : public Animal

{

public:

void meow()

{

cout << “This cat meows.” << endl;

}

};

int main()

{

Dog myDog;

Cat myCat;

// Calling function from the Animal class (base class)

myDog.eat();   // Inherited from Animal

myDog.bark();  // Specific to Dog

myCat.eat();   // Inherited from Animal

myCat.meow();  // Specific to Cat

return 0;

}

Output:

This animal eats food.

This dog barks.

This animal eats food.

This cat meows.

 

In this example:

  • The Animal class is the base class.
  • Both Dog and Cat are derived classes that inherit from the Animal class.
  • Both derived classes (Dog and Cat) can access the eat() method from Animal, and each has its own method (bark() for Dog and meow() for Cat).

Advantages of Hierarchical Inheritance:

  • Code Reusability: Derived classes can reuse common code from the base class, avoiding redundancy.
  • Simplifies Code: Since multiple classes can share common functionality, the code is easier to maintain.
  • Logical Structure: The relationship between classes is clear and logical, making the code easier to understand.

Disadvantages of Hierarchical Inheritance:

  • Tight Coupling: The derived classes are tightly coupled to the base class. Any changes to the base class may affect all the derived classes.
  • Limited Flexibility: If you need to modify or extend a single derived class, it may be difficult if the classes are too dependent on the base class’s behavior.

Hierarchical inheritance is beneficial when multiple classes share common functionality but still have their own specific behaviors. It’s a useful model when building related objects that are conceptually linked by a common parent class. However, it requires careful design to avoid issues with tight coupling and ensure that changes to the base class do not negatively affect all the derived classes.

 

5. Hybrid Inheritance

Hybrid Inheritance in C++ is a type of inheritance that combines two or more types of inheritance. It typically involves a mix of multiple inheritance, multilevel inheritance, and/or hierarchical inheritance. In other words, a class inherits from multiple base classes, and one or more of those base classes might also be derived from other classes.

Key Points of Hybrid Inheritance:

  1. Combination of Inheritance Types: Hybrid inheritance involves mixing multiple inheritance types, such as:
    • Multiple Inheritance: Inheriting from more than one base class.
    • Multilevel Inheritance: Inheriting through multiple levels.
    • Hierarchical Inheritance: Multiple classes inheriting from a single base class.
  2. Complex Relationships: The derived class can inherit features from different classes at various levels, creating a more complex class hierarchy.
  3. Flexibility: It allows more complex and flexible design by combining the benefits of different inheritance types, but it may also introduce potential challenges such as ambiguity.

 

Example of Hybrid Inheritance:

#include <iostream>

using namespace std;

// Base class 1

class Animal

{

public:

void eat()

{

cout << “This animal eats food.” << endl;

}

};

// Base class 2

class Bird

{

public:

void fly()

{

cout << “This bird can fly.” << endl;   

}

};

// Derived class 1 (Multilevel inheritance from Animal)

class Mammal : public Animal

{

public:

void breathe()

{

cout << “This mammal breathes air.” << endl;

}

};

// Derived class 2 (Multiple inheritance from Animal and Bird)

class Bat : public Mammal, public Bird

{

public:

void sleep()

{

cout << “The bat sleeps during the day.” << endl;

}

};

int main()

{

Bat myBat;     // Calling functions from the entire inheritance chain   

myBat.eat();    // From Animal class   

myBat.breathe(); // From Mammal class

myBat.fly();    // From Bird class

myBat.sleep();  // From Bat class

return 0;

}

Output:

This animal eats food.

This mammal breathes air.

This bird can fly.

The bat sleeps during the day.

 

In this example:

  • Bat inherits from both Mammal (which is derived from Animal) and Bird, creating a hybrid inheritance structure.
  • The Bat class combines functionalities from the Animal, Bird, and Mammal classes.

Advantages of Hybrid Inheritance:

  1. Combines Strengths: You can combine the benefits of multiple inheritance types, allowing for greater flexibility and more specialized class behaviors.
  2. Code Reusability: It promotes code reuse from multiple base classes, reducing redundancy.
  3. Extensibility: It allows for creating complex, feature-rich classes by combining different behaviors.

Disadvantages of Hybrid Inheritance:

  1. Complexity: Hybrid inheritance can lead to complex relationships between classes, making the code harder to understand and maintain.
  2. Ambiguity: In cases of multiple inheritance, ambiguity may arise if multiple base classes have methods with the same name, leading to the diamond problem.
    • The diamond problem occurs when a derived class inherits from two classes that share a common base class, and it creates ambiguity regarding which base class method should be called.
    • This problem can be resolved using virtual inheritance.

For example:

class A

{

public:

void display()

{

cout << “Class A display()” << endl;

}

};

class B : public A

{

public:

void display()

{

cout << “Class B display()” << endl;   

}

};

class C : public A

{

public:   

void display()

{

cout << “Class C display()” << endl;

}

};

class D : public B, public C

{

public:

void display()

{

cout << “Class D display()” << endl;

}

};

  1. Increased Risk of Errors: With multiple inheritance, there is a higher risk of errors when multiple classes share functionality or data, especially if the inheritance chain is long.

 

Access Modifiers in Inheritance

In C++, access modifiers define the level of access control for the members (variables and functions) of a class. When it comes to inheritance, the access modifiers used in the base class (parent class) affect how the members are inherited and accessed in the derived class (child class). There are three primary access modifiers in C++:

  1. public
  2. protected
  3. private

Access modifiers determine how the members of the base class are inherited by the derived class and whether they can be accessed outside the class or by other classes. The way these access modifiers are inherited depends on the type of inheritance (i.e., whether the inheritance is public, protected, or private).

Let’s break down each of the access modifiers in the context of inheritance:

 

1. Public Inheritance

In public inheritance, the public and protected members of the base class are inherited as public and protected members in the derived class, respectively. However, private members of the base class are not accessible in the derived class.

  • Public members in the base class become public in the derived class.
  • Protected members in the base class become protected in the derived class.
  • Private members in the base class remain inaccessible in the derived class.

Example:

#include <iostream>

using namespace std;

// Base class

class Animal

{

public:

void eat()

{

cout << “This animal eats food.” << endl;

}

protected:

void sleep()

{

cout << “This animal sleeps.” << endl;

}

private:

void breathe()

{

cout << “This animal breathes.” << endl;

}

};

// Derived class (Public inheritance)

class Dog : public Animal

{

public:

void display()

{

eat();      // Public member (Inherited as public)

sleep();    // Protected member (Inherited as protected)

// breathe(); // Error: Cannot access private member

}

};

int main()

{

Dog myDog;

myDog.display();

// myDog.eat(); // Accessible: eat() is public in Dog

// myDog.sleep();

// Error: sleep() is protected in Dog, not accessible outside

return 0;

}

 

2. Protected Inheritance

In protected inheritance, both the public and protected members of the base class become protected in the derived class. Private members of the base class remain inaccessible.

  • Public members in the base class become protected in the derived class.
  • Protected members in the base class remain protected in the derived class.
  • Private members in the base class remain inaccessible in the derived class.

Example:

#include <iostream>

using namespace std;

// Base class

class Animal

{

public:

void eat()

{

cout << “This animal eats food.” << endl;

}

protected:

void sleep()

{

cout << “This animal sleeps.” << endl;

}

private:

void breathe()

{

cout << “This animal breathes.” << endl;

}

};

// Derived class (Protected inheritance)

class Dog : protected Animal

{

public:

void display()

{

eat();      // Public member (Inherited as protected)

sleep();    // Protected member (Inherited as protected)

// breathe();

// Error: Cannot access private member

}

};

int main()

{

Dog myDog;

myDog.display();

// myDog.eat();

// Error: eat() is protected in Dog, not accessible outside

// myDog.sleep(); // Error: sleep() is protected in Dog, not accessible outside

return 0;

}

 

3. Private Inheritance

In private inheritance, both the public and protected members of the base class become private in the derived class. Private members of the base class remain inaccessible.

  • Public members in the base class become private in the derived class.
  • Protected members in the base class become private in the derived class.
  • Private members in the base class remain inaccessible.

Example:

#include <iostream>

using namespace std;

// Base class

class Animal

{

public:

void eat()

{

cout << “This animal eats food.” << endl;

}

protected:

void sleep()

{

cout << “This animal sleeps.” << endl;

}

private:

void breathe()

{

cout << “This animal breathes.” << endl;

}

};

// Derived class (Private inheritance)

class Dog : private Animal

{

public:

void display()

{

eat();      // Public member (Inherited as private)

sleep();    // Protected member (Inherited as private)

// breathe();

// Error: Cannot access private member

}

};

int main()

{

Dog myDog;

myDog.display();

// myDog.eat();

// Error: eat() is private in Dog, not accessible outside

// myDog.sleep();

// Error: sleep() is private in Dog, not accessible outside

return 0;

}

 

Summary Table of Access Modifiers in Inheritance:

Base Class Member

Public Inheritance

Protected Inheritance

Private Inheritance

Public Member

Public

Protected

Private

Protected Member

Protected

Protected

Private

Private Member

Not Accessible

Not Accessible

Not Accessible

When to Use Each Type of Inheritance:

  • Public Inheritance: It is the most common and widely used form of inheritance. It is used when the derived class “is a” kind of the base class. For example, a Dog “is an” Animal.
  • Protected Inheritance: This is less commonly used but can be useful when you want to restrict the public interface of a derived class while still allowing the derived class to access the inherited members.
  • Private Inheritance: This is used when the derived class should not expose the functionality of the base class to outside code. It implies that the derived class “has a” base class, but is not necessarily a type of it. This is typically used to model a relationship where the derived class implements or uses the base class’s features in a private context.

 

Constructor and Destructor in Inheritance

In C++, constructors and destructors play an essential role in object creation and destruction, respectively. In the context of inheritance, the behavior of constructors and destructors can be slightly more complex because they need to handle the initialization and cleanup of both base and derived class objects.

 

1. Constructor in Inheritance

When a derived class object is created, its constructor is called, but before the derived class’s constructor executes, the constructor of the base class is executed first. This ensures that the base class part of the object is properly initialized before the derived class can initialize its own members.

Key Points About Constructors in Inheritance:

  • Base class constructor is called first before the derived class constructor.
  • Constructor Initialization List: If the base class constructor requires parameters, they must be passed from the derived class’s constructor.
  • Constructor Overloading: If the base class has multiple constructors, the derived class can choose which one to call.

Example of Constructors in Inheritance:

#include <iostream>

using namespace std;

// Base class

class Animal

{

public:

Animal()

{

cout << “Animal constructor called!” << endl;

}

Animal(string name)

{

cout << “Animal constructor with parameter: ” << name << endl;

}

};

// Derived class

class Dog : public Animal

{

public:

Dog()

{

cout << “Dog constructor called!” << endl;

}

Dog(string name) : Animal(name)

{

cout << “Dog constructor with parameter: ” << name << endl;

}

};

int main()

{

cout << “Creating dog1…” << endl;

Dog dog1; // Default constructor, calls Animal’s default constructor

cout << “\nCreating dog2…” << endl;

Dog dog2(“Buddy”); // Parameterized constructor, calls Animal’s parameterized constructor

return 0;

}

Output:

Creating dog1…

Animal constructor called!

Dog constructor called!

Creating dog2…

Animal constructor with parameter: Buddy

Dog constructor with parameter: Buddy

 

In the above example:

  • The default constructor of Animal is called first, followed by the constructor of Dog.
  • When a parameterized constructor is used (Dog(“Buddy”)), the base class constructor with the parameter (Animal(string name)) is called explicitly using the constructor initializer list.

 

2. Destructor in Inheritance

Destructors are used to clean up resources when an object is destroyed. In the context of inheritance:

  • The derived class destructor is called first, followed by the base class destructor.
  • If a class has a virtual destructor, then when an object of a derived class is deleted using a base class pointer, the derived class destructor is called first, and then the base class destructor is called. This ensures proper cleanup of resources in polymorphic scenarios.

Key Points About Destructors in Inheritance:

  • Base class destructor is called automatically after the derived class destructor.
  • Virtual Destructor: If the base class has a virtual destructor, it ensures that destructors are called in the correct order when an object is deleted using a base class pointer.

Example of Destructors in Inheritance:

#include <iostream>

using namespace std;

// Base class

class Animal

{

public:

Animal()

{

cout << “Animal constructor called!” << endl;

}

virtual ~Animal()    // Virtual Destructor

{

cout << “Animal destructor called!” << endl;

}

};

// Derived class

class Dog : public Animal

{

public:

Dog()

{

cout << “Dog constructor called!” << endl;

}

~Dog()    // Destructor

{

cout << “Dog destructor called!” << endl;

}

};

int main()

{

cout << “Creating a dog object…” << endl;

Dog* dog = new Dog(); // Create a Dog object dynamically

cout << “\nDeleting the dog object…” << endl;

delete dog; // This will call the destructor chain

return 0;

}

Output:

Creating a dog object…

Animal constructor called!

Dog constructor called!

Deleting the dog object…

Dog destructor called!

Animal destructor called!

 

In the above example:

  • The virtual destructor in the Animal class ensures that when a Dog object is deleted (using a pointer of type Animal), both the Dog and Animal destructors are called in the correct order.
  • The Dog destructor is called first, followed by the Animal destructor.

 

Destructor Without Virtual Keyword (Non-Polymorphic Case)

If the base class destructor is not declared as virtual, the base class destructor will not be called when the object is deleted through a base class pointer. This can lead to resource leaks or undefined behavior if the base class needs cleanup.

#include <iostream>

using namespace std;

// Base class

class Animal

{

public:

Animal()

{

cout << “Animal constructor called!” << endl;

}

~Animal()    // Non-virtual Destructor

{

cout << “Animal destructor called!” << endl;

}

};

// Derived class

class Dog : public Animal

{

public:

Dog()

{

cout << “Dog constructor called!” << endl;

}

~Dog()   // Destructor

{

cout << “Dog destructor called!” << endl;

}

};

int main()

{

cout << “Creating a dog object…” << endl;

Animal* dog = new Dog(); // Create a Dog object dynamically

cout << “\nDeleting the dog object…” << endl;

delete dog; // Will only call Animal’s destructor

return 0;

}

Output:

Creating a dog object…

Animal constructor called!

Dog constructor called!

Deleting the dog object…

Dog destructor called!

 

In this case:

  • The destructor of Dog is called, but the Animal destructor is not called because the base class destructor is not virtual.

Summary of Constructor and Destructor Behavior in Inheritance:

  • Constructors:
    • The base class constructor is called before the derived class constructor.
    • If the base class constructor requires parameters, the derived class must pass the required arguments via the constructor initializer list.
  • Destructors:
    • The derived class destructor is called first, followed by the base class destructor.
    • If the base class destructor is declared virtual, the destructor calls are handled correctly in polymorphic scenarios (i.e., when deleting a derived class object using a base class pointer).
    • If the base class destructor is not virtual, the derived class destructor is called, but the base class destructor may not be called, potentially causing resource leaks.

You May Like to Browers More