Inheritance in C++ lets a derived class reuse a base class: it keeps the base's data and behaviour and adds or overrides its own. The mechanism is a [[:]] in the class declaration and an initialiser of the base sub-object in the constructor. Inheritance is powerful, but it creates a permanent coupling between the two classes, so the shape of that coupling matters.
Deriving a class
A derived class lists its base with a colon in the declaration.
#include
#include
class Shape {
public:
explicit Shape(const std::string& name) : name_(name) {}
double area() const {
return 0.0;
}
const std::string& name() const {
return name_;
}
private:
std::string name_;
};
class Rectangle : public Shape {
public:
Rectangle(const std::string& name, double width, double height)
: Shape(name), width_(width), height_(height) {}
double area() const {
return width_ * height_;
}
double width() const { return width_; }
double height() const { return height_; }
private:
double width_;
double height_;
};
int main() {
Rectangle box("box", 3.0, 4.0);
std::cout << "name: " << box.name() << '\n';
std::cout << "area: " << box.area() << '\n';
return 0;
}The output shows the derived class using both its own data and the inherited one:
name: box
area: 12The initialiser Shape(name) constructs the base sub-object before the derived members are built. That order is fixed by the language, and it is why a base constructor can safely rely on the base's own members being initialised.
Public, private and protected inheritance
The access specifier in the inheritance clause decides what the derived class exposes to the outside world.
publicinheritance — the base'spublicandprotectedmembers stay public and protected in the derived class. This is the "is-a" relationship.protectedinheritance — the base's public members become protected in the derived class.privateinheritance — the base's public and protected members become private in the derived class. This is the "implemented in terms of" relationship.
#include
#include
class Engine {
public:
int horsepower() const { return horsepower_; }
private:
int horsepower_ = 200;
};
class Car : private Engine {
public:
int output() const {
return horsepower(); // accessible: private inheritance
}
};
int main() {
Car car;
std::cout << "output: " << car.output() << '\n';
// std::cout << car.horsepower(); // does not compile: it is private now
return 0;
}The output confirms the engine's power is reachable inside the class but not outside it:
output: 200The constructor chain
Constructing a derived object runs a chain of constructors: the base first, then the derived members, then the derived body. Destruction runs the other way.
#include
#include
class Shape {
public:
explicit Shape(const std::string& name) {
std::cout << "Shape(" << name << ")\n";
}
virtual ~Shape() {
std::cout << "destroying Shape\n";
}
};
class Rectangle : public Shape {
public:
Rectangle(const std::string& name, double width)
: Shape(name), width_(width) {
std::cout << "Rectangle(" << width << ")\n";
}
private:
double width_;
};
int main() {
Rectangle box("box", 3.0);
std::cout << "in main\n";
return 0;
}The output shows the order in which everything happened:
Shape(box)
Rectangle(3)
in main
destroying ShapeThe base is constructed first, the derived class second, and the derived object is destroyed before the base part of it.
What the derived class inherits
A derived class inherits the base's member functions and data, subject to access. It does not inherit the base's constructors, the base's copy assignment behaviour in the way you might expect, or private members.
It can also *hide* a base member by declaring one with the same name, though that is usually a mistake. The clean way to change a base's behaviour is to override a virtual function, which is the next lesson.
#include
#include
class Shape {
public:
explicit Shape(const std::string& name) : name_(name) {}
const std::string& name() const { return name_; }
std::string describe() const { return "a shape called " + name_; }
private:
std::string name_;
};
class Circle : public Shape {
public:
Circle(const std::string& name, double radius)
: Shape(name), radius_(radius) {}
// Hiding the base member. The base version is still reachable
// with an explicit qualification.
std::string describe() const {
return "a circle called " + name();
}
private:
double radius_;
};
int main() {
Circle disc("disc", 2.0);
std::cout << disc.describe() << '\n';
std::cout << disc.Shape::describe() << '\n';
return 0;
}The output shows the derived version winning by default and the base version still reachable:
a circle called disc
a shape called discHiding is not overriding. The difference matters enormously once you hold a reference or pointer to the base type, which is exactly what the next lesson is about.
When not to use inheritance
Inheritance creates an is-a relationship, and every is-a claim is a permanent promise. A Circle is-a Shape is safe. A Square is-a Rectangle is not, because a square has equal sides and a rectangle does not, so the derived class would have to weaken the base's promises.
Whenever the relationship is "has a", use composition instead. A Car has-an Engine, not is-an.
The rule of thumb is simple: inherit only when the derived class can be used everywhere the base is used, without the caller noticing anything is different. If you find yourself overriding a base method to throw "not supported", you have the wrong hierarchy.
Quick reference
- A derived class keeps the base's data and behaviour and may add its own.
- The base is constructed first, then the derived members, then the derived body.
- Objects are destroyed in reverse order.
publicinheritance keeps the base's interface visible;privatehides it.- Hiding a base member is not the same as overriding it.
- Inherit only for a genuine is-a relationship; use composition for has-a.
Try it yourself: write a [[Shape]] base class with a name and an [[area]] method, then derive a [[Rectangle]] and a [[Circle]] from it, each with its own data and its own area calculation. Print the area of each through a [[Shape]] reference and note that the base version is called. Then add a virtual keyword to the base's area method and watch the output change.