Inheritance in C++ Tutorial: Types, Access Modes and Runnable Examples

Learn what a derived class receives from its base, how access modes transform members, and how construction and virtual dispatch work through runnable examples.

KnowledgeGate Team

Exam prep & CS education

Updated 30 Aug 20265 min read

You can write a C++ class, but what does a derived class actually receive from its base? C++ inheritance involves syntax, access transformation, five inheritance shapes, constructor order, overriding, and common compile errors. The Coding & DSA courses provide a broader learning path alongside OOP.

Inheritance in C++: the base-class and derived-class model

Inheritance constructs a new class from an existing class when the relationship is genuinely "is a". An ElectricCar object contains a Vehicle base subobject. It is not source-code copy and paste.

class ElectricCar : public Vehicle {
    // ElectricCar members
};

Vehicle is the base, ElectricCar is the derived class, and public is the inheritance mode. Private Vehicle data remains in the base subobject, but ElectricCar cannot access it directly.

An ElectricCar is a Vehicle, so inheritance is sensible. A Car has an Engine, so composition with an Engine data member is usually better.

C++ inheritance access modes: public, protected and private

Base member declaration

Public inheritance

Protected inheritance

Private inheritance

public

Stays public

Becomes protected

Becomes private

protected

Stays protected

Stays protected

Becomes private

private

Never directly accessible

Never directly accessible

Never directly accessible

Suppose Vehicle has protected int speedKmph = 60, private int registrationCode = 731, and public int getRegistrationCode() const. Inside ElectricCar::show(), reading speedKmph compiles. Reading registrationCode directly fails, while calling getRegistrationCode() returns 731. In main, car.speedKmph also fails because protected does not mean public.

Watch the default-mode trap. class ElectricCar : Vehicle means private inheritance, while struct ElectricCar : Vehicle means public inheritance. Spell out public when intended.

Types of inheritance in C++: five class shapes

  1. Single: Vehicle -> ElectricCar. One derived class extends one base, which suits a direct specialisation.

  2. Multilevel: Vehicle -> ElectricCar -> AutonomousCar. Each level specialises the previous one.

  3. Hierarchical: Vehicle -> ElectricCar and Vehicle -> DieselCar. Several variants share one base interface.

  4. Multiple: GPSDevice + MusicPlayer -> InfotainmentUnit. One class combines two independent interfaces.

  5. Hybrid or diamond: Device -> Camera, Device -> Phone, then Camera + Phone -> SmartUnit. It can model two roles sharing an ancestor; use virtual public Device when SmartUnit should contain one shared Device base subobject.

A permitted class graph is not automatically a good design. Keep it only when every derived object can safely act as its base.

Five C++ inheritance shapes as class graphs: single, multilevel, hierarchical, multiple and hybrid diamond.

Inheritance in C++ worked example: Vehicle to ElectricCar

This complete C++17 program uses both parts of an ElectricCar:

#include <iostream>

class Vehicle {
protected:
    int speedKmph;

public:
    explicit Vehicle(int speed) : speedKmph(speed) {
        std::cout << "Vehicle constructor\n";
    }

    int distanceInHours(int hours) const {
        return speedKmph * hours;
    }

    virtual ~Vehicle() = default;
};

class ElectricCar : public Vehicle {
private:
    int batteryPercent;

public:
    ElectricCar(int speed, int battery)
        : Vehicle(speed), batteryPercent(battery) {
        std::cout << "ElectricCar constructor\n";
    }

    void drive(int hours) {
        int distance = distanceInHours(hours);
        batteryPercent -= hours * 12;
        std::cout << "Distance: " << distance << " km\n";
        std::cout << "Battery: " << batteryPercent << "%\n";
    }
};

int main() {
    ElectricCar car(60, 80);
    car.drive(2);
}

The exact output is:

Vehicle constructor
ElectricCar constructor
Distance: 120 km
Battery: 56%

The distance is 60 * 2 = 120 km. Battery use is 2 * 12 = 24 percentage points, leaving 80 - 24 = 56%. The run proves that the base constructor executes first, the object uses an inherited public method, and drive() operates on base and derived state.

Construction order and state trace for an ElectricCar built from a Vehicle base, with distance and battery values.

Constructor order, overriding and virtual dispatch

Construction runs in this order: the base constructor, derived data-member initialisation, then the derived constructor body. Destruction runs in reverse. If the destructors print their names, the end-of-scope order is ElectricCar destructor followed by Vehicle destructor:

// In Vehicle
virtual ~Vehicle() { std::cout << "Vehicle destructor\n"; }

// In ElectricCar
~ElectricCar() override { std::cout << "ElectricCar destructor\n"; }

Keep the base destructor virtual because a base pointer may own a derived object. Add virtual int rangeKm() const { return 300; } to Vehicle and this override to ElectricCar:

int rangeKm() const override { return batteryPercent * 4; }

After the two-hour drive, Vehicle* vehicle = &car; followed by vehicle->rangeKm() returns 224, because 56 * 4 = 224. Without virtual in the base and override in the derived class, the call through the base pointer would use the base implementation and return 300.

Overriding matches a virtual base signature. Overloading reuses a name with different parameters. A derived declaration with the same name can also hide other base overloads. Write override so the compiler catches signature mistakes.

Common inheritance errors in C++ and how to fix them

Mistake

What goes wrong

Fix

Access a base private member directly

The derived code does not compile

Use a protected operation or public accessor

Omit public in class inheritance

The base interface becomes private

State the inheritance mode explicitly

Delete through a base with a non-virtual destructor

Deleting through the base pointer has undefined behaviour; derived cleanup is not guaranteed

Make the base destructor virtual

Write Vehicle copy = car

Object slicing removes the ElectricCar part

Use a reference or pointer for polymorphism

The diamond creates another ambiguity. If Device owns int id = 7, and both Camera : public Device and Phone : public Device are non-virtual bases of SmartUnit, the object has two Device subobjects and unit.id is ambiguous. Declare both relationships as virtual public Device; SmartUnit then has one shared Device, and unit.id resolves to 7.

Use inheritance only if every derived object can safely stand in for the base object. Otherwise prefer composition. Inheritance is not automatically faster, cleaner, or more reusable.

How exams and interviews test C++ inheritance

Practise these three trace patterns:

  1. B has public x = 3 and protected y = 4. D : protected B exposes sum() as x + y, so D().sum() returns 7, but d.x is a compile error.

  2. Constructors B(2) then D(5) print B2 D5; destruction prints the derived part before the base part.

  3. The worked base pointer calls the overridden rangeKm() and returns 224, not 300.

Try three short repairs. Build Person(name = "Riya") -> Student(roll = 42) so describe() prints Riya #42. Change class D : B to class D : public B so callers can use B's public interface. Repair the Device(id = 7) diamond with virtual inheritance so only one id exists.

These are common output-prediction, access-checking, and debugging formats. Once they feel routine, Stacks and Queues: LIFO vs FIFO, Postfix, Circular Queue is a useful next coding tutorial for applying class design to core structures.

Inheritance in C++: the short version and next step

  • Model a true is-a relationship.

  • Choose the inheritance mode deliberately.

  • Remember base-before-derived construction.

  • Use virtual plus override for runtime dispatch.

  • Give polymorphic bases a virtual destructor.

Continue the language sequence with the C++ Programming course. If you want C, C++, Java, Python, and placement-oriented coding in one route, Coding for Placements is the broader alternative.