Inheritance

C++ · Lesson 16 of 32 6 min read Updated

A savings account is a kind of bank account. A bus is a kind of vehicle. When one type is a more specific version of another, inheritance lets you write the shared parts once in a base class and add only what is different in a derived class. The derived class automatically gets the base class's data members and member functions.

In this lesson you will create derived classes, call base class constructors, use protected members, understand the order of construction and destruction, and learn when not to use inheritance.

Base and derived classes

You write the base class first. The derived class names its base after a colon, almost always with the keyword public.

C++
#include <print>
#include <string>

class Account {
public:
    Account(const std::string& owner, long long balance)
        : owner_(owner), balance_(balance) {}

    void deposit(long long amount) { balance_ += amount; }
    long long balance() const { return balance_; }
    const std::string& owner() const { return owner_; }

private:
    std::string owner_;

protected:
    long long balance_;          // derived classes may use this directly
};

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

    void add_interest() {
        balance_ += static_cast<long long>(balance_ * rate_ / 100);
    }

private:
    double rate_;
};

int main() {
    SavingsAccount meera("Meera", 20000, 3.5);
    meera.deposit(5000);         // inherited from Account
    meera.add_interest();        // defined in SavingsAccount
    std::println("{}: Rs. {}", meera.owner(), meera.balance());
}
Output
Meera: Rs. 25875

SavingsAccount never declares deposit, balance or owner, yet meera can use all three: they are inherited. After the deposit the balance is 25000, and 3.5 percent interest adds 875.

Look at the constructor: : Account(owner, balance), rate_(rate). A derived class cannot initialise the base class's private members itself, so it passes the values to the base class constructor in its initializer list.

public, protected and private

The three access levels now have a clear meaning:

Member isUsable inside the classUsable in derived classesUsable by other code
publicYesYesYes
protectedYesYesNo
privateYesNoNo

balance_ is protected, so add_interest in the derived class can change it, but code in main cannot. owner_ is private, so even SavingsAccount must use the public owner() function.

The word public in class SavingsAccount : public Account also matters. Public inheritance keeps the base class's public members public, which models "a savings account is an account". There is also protected and private inheritance, which hide the base class's public members from users of the derived class; they are rare, and if you forget the word with class, you get private inheritance by default, a common surprise.

Construction and destruction order

A derived object contains a base part. The base part is built first and destroyed last, like the foundation of a house.

C++
#include <print>

struct Base {
    Base() { std::println("Base built"); }
    ~Base() { std::println("Base destroyed"); }
};

struct Derived : Base {             // struct inherits publicly by default
    Derived() { std::println("Derived built"); }
    ~Derived() { std::println("Derived destroyed"); }
};

int main() {
    Derived d;
    std::println("-- using d --");
}
Output
Base built
Derived built
-- using d --
Derived destroyed
Base destroyed

This order guarantees that while the derived constructor runs, the base part is already complete, and while the derived destructor runs, the base part still exists.

Redefining a member function

A derived class can define a function with the same name as one in the base class. It can still call the base version with Base::name().

C++
#include <print>
#include <string>

class Vehicle {
public:
    explicit Vehicle(const std::string& number) : number_(number) {}
    std::string describe() const { return "Vehicle " + number_; }

protected:
    std::string number_;
};

class Bus : public Vehicle {
public:
    Bus(const std::string& number, int seats) : Vehicle(number), seats_(seats) {}
    std::string describe() const {
        return Vehicle::describe() + " (bus, " + std::to_string(seats_) + " seats)";
    }

private:
    int seats_;
};

int main() {
    Bus city_bus("HR-12-AB-3456", 52);
    std::println("{}", city_bus.describe());

    const Vehicle& v = city_bus;    // a base reference to a derived object
    std::println("{}", v.describe());
}
Output
Vehicle HR-12-AB-3456 (bus, 52 seats)
Vehicle HR-12-AB-3456

The second line is surprising. A Vehicle& may refer to a Bus, because a bus is a vehicle, but calling describe through it runs the Vehicle version. The compiler chose the function by the reference's type, not the real object's type. To get the bus's version you need virtual functions, the subject of Virtual Functions & Polymorphism.

Inheriting constructors and final

When a derived class adds no new constructor logic, using Base::Base; (C++11) inherits the base class's constructors. The keyword final stops anyone from deriving further from a class.

C++
#include <print>
#include <string>

struct Person {
    Person(const std::string& n, int a) : name(n), age(a) {}
    std::string name;
    int age;
};

struct Student final : Person {
    using Person::Person;       // Student("Kabir", 19) now works
    int roll = 0;
};

// struct Monitor : Student {};  // error: Student is final

int main() {
    Student kabir("Kabir", 19);
    kabir.roll = 7;
    std::println("{}, age {}, roll {}", kabir.name, kabir.age, kabir.roll);
}
Output
Kabir, age 19, roll 7

Inheritance or composition?

Use inheritance only for a true "is a" relationship, where the derived object can be used anywhere the base is expected. For "has a" relationships, put an object inside another as a member; this is called composition. A car has an engine, so class Car { Engine engine_; }; is right, while class Car : public Engine would be wrong. Composition is simpler and more flexible, so prefer it when in doubt.

Common mistakes

  • Forgetting public when inheriting with class. class Bus : Vehicle uses private inheritance, so city_bus.describe() from outside fails to compile.
  • Not calling the base constructor. If the base has no default constructor, the derived constructor must call one in its initializer list.
  • Inheriting just to reuse code. If "is a" does not make sense in plain English, use composition.
  • Expecting base references to call derived functions. Without virtual, the reference type decides which function runs.

Key points

  • A derived class (class D : public B) inherits the members of its base class and adds its own.
  • Derived constructors pass values to base constructors in the initializer list.
  • protected members are visible to derived classes but not to other code.
  • Bases are constructed first and destroyed last.
  • Without virtual, calling through a base reference runs the base class version.
  • using Base::Base; inherits constructors; final blocks further inheritance; prefer composition for "has a".

Exercise

Create a base class Employee with a name and a monthly salary, and a const function yearly_pay() that returns salary × 12. Derive Manager, which adds a yearly bonus, and give it a function total_pay() that returns yearly_pay() plus the bonus. Create Ananya as a manager earning Rs. 85000 a month with a bonus of Rs. 150000 and print both values.

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

class Employee {
public:
    Employee(const std::string& name, long long monthly)
        : name_(name), monthly_(monthly) {}
    long long yearly_pay() const { return monthly_ * 12; }
    const std::string& name() const { return name_; }

private:
    std::string name_;
    long long monthly_;
};

class Manager : public Employee {
public:
    Manager(const std::string& name, long long monthly, long long bonus)
        : Employee(name, monthly), bonus_(bonus) {}
    long long total_pay() const { return yearly_pay() + bonus_; }

private:
    long long bonus_;
};

int main() {
    Manager ananya("Ananya", 85000, 150000);
    std::println("{}: yearly Rs. {}, with bonus Rs. {}", ananya.name(), ananya.yearly_pay(), ananya.total_pay());
}
Output
Ananya: yearly Rs. 1020000, with bonus Rs. 1170000

total_pay calls the inherited yearly_pay directly, because public members of the base are available inside the derived class.

Quick Quiz

1. Which members of a base class can a derived class use directly?

2. In what order are a derived object's parts constructed?

3. What does class Bus : Vehicle { ... }; (without public) mean?

4. A car has an engine. How should you model this?

Learn C++ with a mentor

Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call