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.
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#
#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";
}Tata with 4 wheels
Tata says beep!
5 seatsclass Car : public Vehicledeclares thatCarinherits fromVehicle.Cargetsdescribe()for free and addshonk()andseats_.- The derived constructor calls the base constructor in its initializer list:
Vehicle(std::move(brand), 4).
public, protected and private members#
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:
#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';
}Ravi: 50000
Meera: 95000Employee::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:
#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";
}Base constructed
Derived constructed
using d
Derived destroyed
Base destroyedThis 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:
#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();
}breathing
feeding young
woofKeep 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.
#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();
}engine started
drivingA 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
#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';
}Asha: 10400SavingsAccount uses only the public interface of Account, so the base class keeps full control of its balance.
Key takeaway#
Finished this lesson?