Inheritance
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.
#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());
}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 is | Usable inside the class | Usable in derived classes | Usable by other code |
|---|---|---|---|
public | Yes | Yes | Yes |
protected | Yes | Yes | No |
private | Yes | No | No |
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.
#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 --");
}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().
#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());
}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.
#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);
}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
publicwhen inheriting withclass.class Bus : Vehicleuses private inheritance, socity_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.
protectedmembers 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;finalblocks 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
#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());
}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?
private members stay hidden even from derived classes; protected exists exactly for this purpose.
2. In what order are a derived object's parts constructed?
The base is built first and destroyed last.
3. What does class Bus : Vehicle { ... }; (without public) mean?
With class, inheritance is private by default; with struct it is public.
4. A car has an engine. How should you model this?
"Has a" relationships use composition; inheritance is for "is a".
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
