Inheritance in C++: Types, Examples & Virtual Functions

Inheritance in C++ is an object-oriented programming concept that allows a derived class to reuse properties and functions from a base class. The five types are single, multilevel, multiple, hierarchical and hybrid inheritance. Understanding access specifiers, constructors, virtual functions, runtime polymorphism and the diamond problem helps developers write reusable and maintainable C++ programs.

Imagine you are building a software system for a company. You have employees, managers and engineers. All of them have a name, an ID and a salary, but a manager also has a team and an engineer also has a programming skill. Writing the same name, ID and salary code three times would be wasteful. C++ inheritance solves this problem by allowing a new class to reuse the properties and behaviours of an existing class. Inheritance is one of the four pillars of object-oriented programming in C++. Understanding inheritance in C++ with examples is essential for writing clean, reusable and maintainable code.

Inheritance in C++_ Types, Examples & Virtual Functions

What Is Inheritance in C++?

Inheritance in C++ is an object-oriented programming concept in which a new class, called the derived class or child class, acquires accessible properties and behaviours from an existing class, called the base class or parent class.

The derived class can reuse existing members and introduce additional data members and member functions. It can also override inherited virtual functions to provide specialized behaviour.

Inheritance typically represents an is-a relationship. For example, a Manager is an Employee, and a Dog is an Animal.

The main advantages of inheritance in C++ include:

  • Code reusability: Reuse common functionality without writing the same code repeatedly.
  • Maintainability: Update shared functionality in the base class.
  • Extensibility: Add specialized functionality to derived classes.
  • Runtime polymorphism: Use virtual functions to achieve dynamic dispatch through base-class pointers or references.

Basic C++ Inheritance Syntax

The syntax for inheritance in C++ uses a colon (:) after the derived class name, followed by an inheritance access mode and the base class name.

#include 
#include 
using namespace std;

class Employee {
protected:
    string name;
    int id;

public:
    Employee(string n, int i) : name(n), id(i) {}

    void show() {
        cout << id << " " << name << endl;
    }
};

class Manager : public Employee {
    int teamSize;

public:
    Manager(string n, int i, int t)
        : Employee(n, i), teamSize(t) {}

    void showTeam() {
        cout << "Team: " << teamSize << endl;
    }
};

int main() {
    Manager m("Arun", 101, 5);

    m.show();
    m.showTeam();

    return 0;
}

Output:

101 Arun
Team: 5

In this example, Employee is the base class, and Manager is the derived class. The Manager class inherits publicly from Employee, so its objects can call the public show() function inherited from the base class. The showTeam() function belongs specifically to Manager.

The protected members name and id are accessible inside Manager, but they cannot be accessed directly from ordinary external code.

registor_now_P

Types of Inheritance in C++

C++ supports five commonly discussed types of inheritance: single, multilevel, multiple, hierarchical and hybrid inheritance.

1. Single Inheritance in C++

Single inheritance occurs when one derived class inherits from one base class.

Example: Manager inherits from Employee.

class Employee {
public:
    void work() {
        cout << "Employee is working";
    }
};

class Manager : public Employee {
public:
    void manage() {
        cout << "Managing a team";
    }
};

The Manager class can use the accessible work() function inherited from Employee and define its own manage() function.

2. Multilevel Inheritance in C++

Multilevel inheritance creates a chain of inheritance in which one class derives from another derived class.

For example, Animal → Mammal → Dog.

class Animal {
public:
    void eat() {
        cout << "Eating";
    }
};

class Mammal : public Animal {};

class Dog : public Mammal {
public:
    void bark() {
        cout << "Barking";
    }
};

A Dog object can access the public eat() function inherited through Mammal and use its own bark() function.

3. Multiple Inheritance in C++

Multiple inheritance allows one class to inherit from two or more base classes. C++ supports this feature, unlike Java and C#, which do not support multiple inheritance of classes.

class Printer {
public:
    void print() {
        cout << "Printing";
    }
};

class Scanner {
public:
    void scan() {
        cout << "Scanning";
    }
};

class AllInOne : public Printer, public Scanner {};

An AllInOne object can use both print() and scan(). However, if the base classes contain conflicting member names, the program may require explicit qualification or other techniques to resolve ambiguity.

4. Hierarchical Inheritance in C++

Hierarchical inheritance occurs when multiple derived classes inherit from the same base class.

class Employee {
public:
    void attendMeeting() {
        cout << "Attending meeting";
    }
};

class Manager : public Employee {};
class Engineer : public Employee {};

Both Manager and Engineer can reuse the functionality provided by Employee while adding their own specialized behaviour.

5. Hybrid Inheritance in C++

Hybrid inheritance combines two or more inheritance patterns, such as multiple and multilevel inheritance. It can create complex class hierarchies and may introduce ambiguity when the same base class is inherited through different paths.

Virtual inheritance can help solve the diamond problem in certain hybrid inheritance structures.

Access Specifiers in C++ Inheritance

Access specifiers and inheritance modes determine how base-class members can be accessed from a derived class and from external code.

The three access specifiers are public, protected and private. The inheritance modes are also public, protected and private.

Base-class memberPublic inheritanceProtected inheritancePrivate inheritance
publicPublicProtectedPrivate
protectedProtectedProtectedPrivate
privateNot directly accessibleNot directly accessibleNot directly accessible
Important: Private members of the base class remain part of the base-class subobject, but derived classes cannot access them directly. They may still interact with them through accessible base-class member functions.

Public inheritance is generally preferred when the derived class genuinely represents an is-a relationship. Protected and private inheritance are more specialized techniques.

Constructors and Destructors in C++ Inheritance

Constructors and destructors control how objects are initialized and cleaned up in an inheritance hierarchy.

When a derived object is created, its base-class constructor runs first, followed by the derived-class constructor. During destruction, the order is reversed: the derived-class destructor runs first, followed by the base-class destructor.

Construction: Base → Derived  |  Destruction: Derived → Base

Consider the earlier Manager example:

Manager(string n, int i, int t)
    : Employee(n, i), teamSize(t) {}

The expression Employee(n, i) explicitly invokes the appropriate base-class constructor. This is necessary because Employee does not provide a default constructor.

If the derived constructor does not explicitly initialize the base class, C++ attempts to use an accessible default constructor when one is available.

Understanding constructors and destructors in C++ inheritance helps prevent initialization errors and supports reliable resource management.

Explore Courses - Learn More

Function Overriding and Virtual Functions in C++

Function overriding in C++ occurs when a derived class provides its own implementation of an inherited virtual member function with a matching signature.

Virtual functions enable runtime polymorphism in C++, allowing a function call through a base-class pointer or reference to invoke the implementation belonging to the actual object type.

#include 
using namespace std;

class Shape {
public:
    virtual void draw() {
        cout << "Shape" << endl;
    }

    virtual ~Shape() = default;
};

class Circle : public Shape {
public:
    void draw() override {
        cout << "Circle" << endl; } }; int main() { Shape* s = new Circle(); s->draw();  // Prints Circle

    delete s;
    return 0;
}

Output:

Circle

Because draw() is virtual, the call s->draw() invokes Circle::draw() even though s is a pointer to Shape.

The override keyword, introduced in C++11, tells the compiler that the function is intended to override a base-class virtual function. If the signature does not match an overridable function, compilation fails.

The virtual destructor is equally important. Deleting a derived object through a base-class pointer when the base destructor is non-virtual results in undefined behaviour. A polymorphic base class intended for such use should generally have a public virtual destructor, or an appropriately restricted destructor design.

Object Slicing in C++

Object slicing in C++ occurs when a derived-class object is copied into a base-class object by value. The derived-specific portion is not retained in the new base-class object.

For example:

class Animal {
public:
    virtual void sound() {
        cout << "Animal sound";
    }
};

class Dog : public Animal {
public:
    void sound() override {
        cout << "Bark";
    }
};

int main() {
    Dog dog;
    Animal animal = dog;

    animal.sound();  // Prints Animal sound
}

Here, animal is a separate Animal object. The Dog-specific portion is sliced away, so the virtual call uses Animal::sound().

To preserve polymorphic behaviour, pass or store objects through base-class pointers or references rather than copying derived objects into base-class objects by value. Smart pointers such as std::unique_ptr and std::shared_ptr can help manage dynamically allocated objects safely.

The Diamond Problem in C++

The diamond problem in C++ occurs when a class inherits from two classes that both inherit from the same base class. Without virtual inheritance, the final derived class can contain two separate base-class subobjects, making access to shared base members ambiguous.

class A {
public:
    int value;
};

class B : virtual public A {};
class C : virtual public A {};

class D : public B, public C {};

In this example, B and C use virtual inheritance. As a result, a D object contains one shared A subobject rather than separate A subobjects inherited through both paths.

int main() {
    D obj;
    obj.value = 10;

    cout << obj.value;
}

Output:

10

Virtual inheritance is useful when a hierarchy requires multiple inheritance paths to share a single common base-class subobject. The most-derived class is responsible for initializing a virtual base class.

Public, Protected and Private Inheritance

The three inheritance modes in C++ differ in how they expose inherited public and protected members.

  • Public inheritance: Preserves public members as public and protected members as protected. It is commonly used to model an is-a relationship.
  • Protected inheritance: Makes inherited public and protected members protected within the derived class.
  • Private inheritance: Makes inherited public and protected members private within the derived class.

Private members of the base class are not directly accessible through any of these modes.

For example, class Manager : public Employee allows code using a Manager object to call the inherited public functions of Employee. Changing the mode to private inheritance changes the accessibility of those inherited public functions.

Composition vs Inheritance in C++

Inheritance is not the best solution for every relationship. Composition vs inheritance in C++ is an important design consideration when building maintainable software.

Inheritance represents an is-a relationship, while composition represents a has-a relationship.

For example, a Manager is an Employee, but a Car has an Engine. The latter relationship is usually modelled through composition.

class Engine {
public:
    void start() {
        cout << "Engine started";
    }
};

class Car {
private:
    Engine engine;

public:
    void startCar() {
        engine.start();
    }
};

Here, Car contains an Engine object and uses its functionality without inheriting from Engine.

Prefer composition when a class needs to use another class’s functionality without representing a true subtype relationship. This helps avoid unnecessary coupling and overly complicated inheritance hierarchies.

Best Practices for C++ Inheritance

Follow these practices when designing classes and using inheritance in C++:

  • Use public inheritance when the derived class genuinely satisfies the is-a relationship.
  • Prefer composition for has-a relationships, such as a car having an engine.
  • Declare a virtual destructor in polymorphic base classes that may be deleted through base pointers.
  • Use the override keyword on overriding functions.
  • Avoid unnecessarily deep inheritance hierarchies because they can become difficult to maintain.
  • Avoid copying polymorphic derived objects into base-class objects by value.
  • Use smart pointers where dynamic object ownership is required.
  • Keep base classes focused on shared responsibilities and common interfaces.

Conclusion

Inheritance in C++ enables developers to build new classes from existing classes, reduce code duplication and implement runtime polymorphism. Understanding the types of inheritance in C++, access specifiers, constructors, virtual functions, object slicing and the diamond problem provides a strong foundation for object-oriented programming.

Use inheritance when a genuine is-a relationship exists, and consider composition when one class contains or uses another. Applying these principles helps you design C++ programs that are reusable, understandable and easier to extend.

Talk to Academic Advisor

Frequently Asked Questions

The five commonly discussed types are single, multilevel, multiple, hierarchical and hybrid inheritance. Each describes a different way of organizing relationships between base and derived classes.

Virtual functions enable runtime polymorphism. When called through a base-class pointer or reference, the program can execute the overridden implementation belonging to the actual derived object.

Inheritance represents an is-a relationship, such as a Manager being an Employee. Composition represents a has-a relationship, such as a Car containing an Engine object. Choosing the correct relationship improves code organization and maintainability.

Author

Embedded Systems and IOT Trainer– IIES

Updated On: 27-10-26


10+ years of hands-on experience delivering practical training in Embedded Systems and it's design