Skip to content
Laviyx Learn

Try

C++ course menu

C++ Inheritance: Base and Derived Classes, protected, and Constructor Order

Learn inheritance in C++: deriving a class from a base class, public inheritance and 'is-a' relationships, protected members, calling base constructors, overriding methods, construction and destruction order, and composition vs inheritance.

Intermediate13 min readUpdated
On this page

Inheritance lets you create a new class from an existing one. The new derived class gets all the members of the base class, and adds or changes what it needs. Use it to model an "is-a" relationship: a SavingsAccount is an Account, a Car is a Vehicle.

A base and a derived class#

C++
#include <iostream>
#include <string>

class Vehicle {
public:
    Vehicle(std::string brand, int wheels) : brand_(std::move(brand)), wheels_(wheels) {}

    void describe() const {
        std::cout << brand_ << " with " << wheels_ << " wheels\n";
    }

protected:
    std::string brand_;
    int wheels_;
};

class Car : public Vehicle {
public:
    Car(std::string brand, int seats) : Vehicle(std::move(brand), 4), seats_(seats) {}

    void honk() const {
        std::cout << brand_ << " says beep!\n";   // brand_ is protected: accessible here
    }

    int seats() const { return seats_; }

private:
    int seats_;
};

int main() {
    Car car("Tata", 5);
    car.describe();          // inherited from Vehicle
    car.honk();              // Car's own method
    std::cout << car.seats() << " seats\n";
}
Output
Tata with 4 wheels
Tata says beep!
5 seats
  • class Car : public Vehicle declares that Car inherits from Vehicle.
  • Car gets describe() for free and adds honk() and seats_.
  • The derived constructor calls the base constructor in its initializer list: Vehicle(std::move(brand), 4).

public, protected and private members#

Member in the baseAccessible in the baseIn derived classesOutside code
publicYesYesYes
protectedYesYesNo
privateYesNoNo

protected is useful for data that subclasses need. But it weakens encapsulation: every subclass can break the base class's invariants. Many developers keep data private and give subclasses protected methods instead.

Almost always use public inheritance (: public Base). private and protected inheritance exist, but they model "is implemented using", which composition usually expresses better.

Overriding a method#

A derived class can provide its own version of a base method:

C++
#include <iostream>
#include <string>

class Employee {
public:
    Employee(std::string name, double salary) : name_(std::move(name)), salary_(salary) {}
    double pay() const { return salary_; }
    std::string name() const { return name_; }

protected:
    std::string name_;
    double salary_;
};

class Manager : public Employee {
public:
    Manager(std::string name, double salary, double bonus)
        : Employee(std::move(name), salary), bonus_(bonus) {}

    double pay() const { return Employee::pay() + bonus_; }   // reuse the base version

private:
    double bonus_;
};

int main() {
    Employee e("Ravi", 50000);
    Manager m("Meera", 80000, 15000);
    std::cout << e.name() << ": " << e.pay() << '\n';
    std::cout << m.name() << ": " << m.pay() << '\n';
}
Output
Ravi: 50000
Meera: 95000

Employee::pay() calls the base class's version explicitly. There is a catch: because pay is not virtual, calling it through an Employee& that refers to a Manager would run Employee::pay. Fixing that is what Polymorphism and Virtual Functions is about.

Construction and destruction order#

The base part of an object is built first and destroyed last:

C++
#include <iostream>

class Base {
public:
    Base() { std::cout << "Base constructed\n"; }
    ~Base() { std::cout << "Base destroyed\n"; }
};

class Derived : public Base {
public:
    Derived() { std::cout << "Derived constructed\n"; }
    ~Derived() { std::cout << "Derived destroyed\n"; }
};

int main() {
    Derived d;
    std::cout << "using d\n";
}
Output
Base constructed
Derived constructed
using d
Derived destroyed
Base destroyed

This makes sense: the derived constructor may use base members, so they must already exist; and the derived destructor may still use them, so they are destroyed afterwards.

Inheritance chains#

A derived class can itself be a base class:

C++
#include <iostream>

class Animal {
public:
    void breathe() const { std::cout << "breathing\n"; }
};

class Mammal : public Animal {
public:
    void feed_young() const { std::cout << "feeding young\n"; }
};

class Dog : public Mammal {
public:
    void bark() const { std::cout << "woof\n"; }
};

int main() {
    Dog d;
    d.breathe();
    d.feed_young();
    d.bark();
}
Output
breathing
feeding young
woof

Keep hierarchies shallow. Deep chains are hard to understand, because a class's behaviour is spread over many files.

Composition vs inheritance#

Inheritance is only right for a true "is-a" relationship. When one object has or uses another, use composition: make it a member.

C++
#include <iostream>

class Engine {
public:
    void start() const { std::cout << "engine started\n"; }
};

class Car {
public:
    void drive() const {
        engine_.start();
        std::cout << "driving\n";
    }

private:
    Engine engine_;   // a Car HAS an Engine; it is not an Engine
};

int main() {
    Car c;
    c.drive();
}
Output
engine started
driving

A popular guideline is "prefer composition over inheritance": it is more flexible, and changes to one class do not ripple through a hierarchy.

Multiple inheritance#

C++ allows a class to inherit from several bases (class Amphibian : public Car, public Boat). It is powerful but can lead to ambiguities, for example when both bases have a member with the same name. The common, safe use is inheriting one implementation class plus several interfaces (classes with only pure virtual functions), covered in Abstract Classes.

Common mistakes#

Practice#

Practice exercise

Create a base class Account with an owner and balance, a deposit method and a balance() getter. Derive SavingsAccount, which adds an interest rate and a method add_interest() that deposits balance * rate. Create a savings account for "Asha" with 10000 and a 4% rate, add interest, and print the balance.

Show solution
C++
#include <iostream>
#include <string>

class Account {
public:
    Account(std::string owner, double balance) : owner_(std::move(owner)), balance_(balance) {}
    void deposit(double amount) {
        if (amount > 0) balance_ += amount;
    }
    double balance() const { return balance_; }
    const std::string& owner() const { return owner_; }

private:
    std::string owner_;
    double balance_;
};

class SavingsAccount : public Account {
public:
    SavingsAccount(std::string owner, double balance, double rate)
        : Account(std::move(owner), balance), rate_(rate) {}

    void add_interest() { deposit(balance() * rate_); }

private:
    double rate_;
};

int main() {
    SavingsAccount s("Asha", 10000, 0.04);
    s.add_interest();
    std::cout << s.owner() << ": " << s.balance() << '\n';
}
Output
Asha: 10400

SavingsAccount uses only the public interface of Account, so the base class keeps full control of its balance.

Key takeaway#

Finished this lesson?