Skip to content

Inheritance

Inheritance and composition are fundamental OOP concepts that enable code reuse and establish “is-a” and “has-a” relationships between classes. This chapter covers how to create class hierarchies in C++.

Inheritance allows a class (derived class) to inherit properties and behaviors from another class (base class):

┌─────────────────────────────────────────────────────────────┐
│ Inheritance Hierarchy │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Animal │ ← Base Class │
│ │ (name, age) │ │
│ │ +eat() │ │
│ │ +sleep() │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Dog │ │ Cat │ │ Bird │ │
│ │ +bark() │ │ +meow() │ │ +sing() │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ Derived Classes │
│ │
└─────────────────────────────────────────────────────────────┘
// Base class
class Animal {
protected: // Accessible in derived classes
std::string name;
int age;
public:
Animal(const std::string& n, int a) : name(n), age(a) {}
void eat() {
std::cout << name << " is eating\n";
}
void sleep() {
std::cout << name << " is sleeping\n";
}
void display() const {
std::cout << "Name: " << name << ", Age: " << age << "\n";
}
};
// Derived class
class Dog : public Animal {
private:
std::string breed;
public:
Dog(const std::string& n, int a, const std::string& b)
: Animal(n, a), breed(b) {}
void bark() {
std::cout << name << " says: Woof!\n";
}
void displayInfo() const {
display(); // Call base class method
std::cout << "Breed: " << breed << "\n";
}
};
int main() {
Dog dog("Buddy", 3, "Golden Retriever");
dog.eat(); // Inherited from Animal
dog.sleep(); // Inherited from Animal
dog.bark(); // Specific to Dog
dog.displayInfo();
}
Base AccessPublic InheritanceProtected InheritancePrivate Inheritance
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateinaccessibleinaccessibleinaccessible
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
class PublicDerived : public Base {
// publicVar is public
// protectedVar is protected
// privateVar is inaccessible
};
class PrivateDerived : private Base {
// All become private
};

Most common: Public inheritance (“is-a” relationship)

Base class constructors are called before derived class constructors:

class Base {
public:
Base() { std::cout << "Base constructor\n"; }
~Base() { std::cout << "Base destructor\n"; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived constructor\n"; }
~Derived() { std::cout << "Derived destructor\n"; }
};
int main() {
Derived d;
}

Output:

Base constructor
Derived constructor
Derived destructor
Base destructor
class Animal {
public:
Animal(const std::string& n) : name(n) {}
protected:
std::string name;
};
class Dog : public Animal {
public:
Dog(const std::string& n, const std::string& b)
: Animal(n), breed(b) {} // Calls Animal(n)
private:
std::string breed;
};

Derived classes can override base class functions:

class Shape {
public:
virtual void draw() {
std::cout << "Drawing a shape\n";
}
virtual ~Shape() {}
};
class Circle : public Shape {
public:
void draw() override { // Override keyword (C++11)
std::cout << "Drawing a circle\n";
}
};
class Square : public Shape {
public:
void draw() override {
std::cout << "Drawing a square\n";
}
};
int main() {
Shape* shapes[] = {new Circle(), new Square()};
for (auto* s : shapes) {
s->draw(); // Polymorphic call
}
}

Output:

Drawing a circle
Drawing a square

Use virtual to enable polymorphism:

class Base {
public:
void nonVirtual() { std::cout << "Base non-virtual\n"; }
virtual void virtualFunc() { std::cout << "Base virtual\n"; }
virtual ~Base() {}
};
class Derived : public Base {
public:
void nonVirtual() { std::cout << "Derived non-virtual\n"; }
void virtualFunc() override { std::cout << "Derived virtual\n"; }
};
int main() {
Base* ptr = new Derived();
ptr->nonVirtual(); // "Base non-virtual" (static binding)
ptr->virtualFunc(); // "Derived virtual" (dynamic binding)
delete ptr;
}

Composition models “has-a” relationships where one class contains another:

class Engine {
private:
int horsepower;
public:
Engine(int hp) : horsepower(hp) {}
void start() { std::cout << "Engine started\n"; }
int getHorsepower() const { return horsepower; }
};
class Car {
private:
std::string model;
Engine engine; // Car "has an" Engine
public:
Car(const std::string& m, int hp)
: model(m), engine(hp) {}
void start() {
std::cout << model << " starting...\n";
engine.start();
}
};
int main() {
Car car("Tesla Model 3", 283);
car.start();
}
InheritanceComposition
”is-a” relationship”has-a” relationship
Tightly coupledLoosely coupled
Harder to change at runtimeFlexible, can change at runtime
Use when true “is-a”Use when “has-a” or “uses”

Guidelines:

  • Prefer composition over inheritance
  • Use inheritance for true “is-a” relationships
  • Use composition for “has-a” relationships
// Inheritance: Dog IS an Animal
class Dog : public Animal { };
// Composition: Car HAS an Engine
class Car {
Engine engine; // Not inheritance
};

A class can inherit from multiple base classes:

class Printable {
public:
virtual void print() const = 0;
};
class Loggable {
public:
virtual void log() const = 0;
};
class Document : public Printable, public Loggable {
public:
void print() const override {
std::cout << "Printing document\n";
}
void log() const override {
std::cout << "Logging document\n";
}
};

When a class inherits from two classes that both inherit from a common base:

A
/ \
B C
\ /
D

Solution: Virtual inheritance

class A {
public:
int value;
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {}; // Only one A
#include <iostream>
#include <vector>
#include <memory>
// Abstract base class
class Shape {
protected:
std::string color;
public:
Shape(const std::string& c) : color(c) {}
// Pure virtual function - must be overridden
virtual double area() const = 0;
virtual void draw() const = 0;
virtual ~Shape() {}
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r, const std::string& c) : Shape(c), radius(r) {}
double area() const override {
return 3.14159 * radius * radius;
}
void draw() const override {
std::cout << "Drawing circle with radius " << radius
<< " and color " << color << "\n";
}
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h, const std::string& c)
: Shape(c), width(w), height(h) {}
double area() const override {
return width * height;
}
void draw() const override {
std::cout << "Drawing rectangle " << width << "x" << height
<< " with color " << color << "\n";
}
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5, "Red"));
shapes.push_back(std::make_unique<Rectangle>(4, 6, "Blue"));
double totalArea = 0;
for (const auto& shape : shapes) {
shape->draw();
totalArea += shape->area();
}
std::cout << "Total area: " << totalArea << "\n";
return 0;
}
  • Inheritance creates “is-a” relationships between classes
  • Use public inheritance for typical inheritance
  • Base class constructors run before derived class constructors
  • Use virtual functions for polymorphism
  • Use override keyword to catch errors
  • Prefer composition (“has-a”) over inheritance (“is-a”)
  • Use virtual inheritance to solve the diamond problem
  • Abstract classes with pure virtual functions define interfaces

Let’s continue learning about polymorphism in more detail.

Next Chapter: 11_polymorphism.md - Polymorphism and Virtual Functions