New to C++? This guide explains Inheritance in C++ with relatable analogies, clean code, and “Try it yourself” challenges after each section to build muscle memory. Think of inheritance like family traits: a child inherits features from parents, but can also have unique qualities—code works the same way.
In simple terms, Inheritance in C++ lets one class (the derived/child class) reuse and extend another class (the base/parent class). It reduces duplication, encourages reuse, and builds clean hierarchies where common behavior lives in one place.
🔹 What is Inheritance in C++?
Inheritance allows a derived class to acquire attributes and methods from a base class. The derived class can add new members or override behavior (when using virtual
functions). Analogy: a “Car” is a “Vehicle”—it gets wheels and movement from Vehicle, and adds features like honk or AC.
- Base (parent) class: the class being inherited from.
- Derived (child) class: the class that extends/uses the base’s features.
- “is‑a” rule: only use inheritance when the relationship truly is “is‑a.”
Try it yourself
- Write 3 examples of true “is‑a” relationships (e.g., “Student is a Person”).
- Write 2 examples that are not “is‑a” (e.g., “Car has an Engine”)—these are composition, not inheritance.
🔹 Basic Syntax and Public Inheritance
The general syntax is class Derived : public Base { ... };
. Public inheritance means the base’s public interface stays public in the derived class, and protected stays protected. This is the most common and recommended mode for modeling “is‑a” relationships in Inheritance in C++.
#include <algorithm>
#include <iostream>
using namespace std;
// Base class
class Animal {
public:
void eat() const { cout << "Eating...\n"; }
void sleep() const { cout << "Sleeping...\n"; }
};
// Derived class using public inheritance
class Dog : public Animal {
public:
void bark() const { cout << "Woof!\n"; }
};
int main() {
Dog d;
d.eat(); // inherited
d.sleep(); // inherited
d.bark(); // own method
}
Output
Eating...
Sleeping...
Woof!
Try it yourself
- Create a
Cat
class derived fromAnimal
with ameow()
method and call all methods. - Change
Dog
to inheritprotected
and observe which members remain accessible frommain()
.
🔹 Access Modes: public, protected, private Inheritance
In Inheritance in C++, the inheritance mode affects how base members appear in the derived class’s interface:
- public: base public → public; base protected → protected.
- protected: base public/protected → protected.
- private: base public/protected → private (default for
class
if unspecified).
#include <iostream>
using namespace std;
class Base {
public:
void pub() {}
protected:
void pro() {}
private:
void pri() {}
};
class Dpub : public Base {
void test() {
pub(); // OK
pro(); // OK
// pri(); // ERROR: private in Base
}
};
class Dpro : protected Base {
void test() {
pub(); // OK (now protected)
pro(); // OK (protected)
}
};
class Dpri : private Base {
void test() {
pub(); // OK (now private)
pro(); // OK (now private)
}
};
Try it yourself
- Create objects of
Dpub
,Dpro
,Dpri
and test which inherited members are callable frommain()
. - Explain in a comment why public inheritance is preferred for real “is‑a” relationships.
🔹 Method Overriding and Polymorphism
To change base behavior in the derived class, mark base methods virtual
and use override
in derived methods. Polymorphism lets base pointers call the correct derived implementation at runtime—key to flexible Inheritance in C++.
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class Animal {
public:
virtual ~Animal() = default; // virtual destructor for polymorphic base
virtual void speak() const { cout << "Some sound\n"; }
};
class Dog : public Animal {
public:
void speak() const override { cout << "Woof!\n"; }
};
class Cat : public Animal {
public:
void speak() const override { cout << "Meow!\n"; }
};
int main() {
vector<unique_ptr<Animal>> zoo;
zoo.push_back(make_unique<Dog>());
zoo.push_back(make_unique<Cat>());
for (const auto& a : zoo)
a->speak(); // Woof!, Meow!
}
Output
Woof!
Meow!
Try it yourself
- Add a
Cow
class overridingspeak()
. Insert into the vector and verify output. - Remove
virtual
from the base destructor briefly (for learning) and observe risky behavior—then restore it.
🔹 Types of Inheritance
Inheritance in C++ supports multiple patterns; choose the one that matches the domain relationship and keeps code clear.
- Single: one base → one derived.
- Multilevel: chain of inheritance (A → B → C).
- Multiple: one derived inherits from multiple bases.
- Hierarchical: multiple derived classes share one base.
- Hybrid: a combination of the above (e.g., multiple + multilevel).
#include <iostream>
using namespace std;
// Single
class Vehicle { /* ... */ };
class Car : public Vehicle { /* ... */ };
// Multilevel
class RacingCar : public Car { /* ... */ };
// Multiple
class Flyable { public: void fly() const {} };
class Floatable { public: void afloat() const {} };
class AmphibiousPlane : public Flyable, public Floatable {};
// Hierarchical
class Truck : public Vehicle {};
class Bike : public Vehicle {};
Use multiple inheritance with care—ambiguities can arise if two bases provide the same member names. Prefer interfaces (pure virtual bases) or composition if practical.
Try it yourself
- Create
Vehicle
→Car
→ElectricCar
(multilevel). Add a method at each level and call them all. - Implement a small multiple inheritance example and resolve name clashes using scope resolution (e.g.,
Base1::method()
).
🔹 The Diamond Problem and Virtual Inheritance
In a diamond, a class inherits the same base through multiple paths, creating duplicate base subobjects. Virtual inheritance shares one common base to avoid duplication and ambiguity.
#include <iostream>
using namespace std;
class Animal {
public:
void live() const { cout << "Living...\n"; }
};
class Mammal : virtual public Animal {};
class Bird : virtual public Animal {};
class Bat : public Mammal, public Bird {};
int main() {
Bat b;
b.live(); // OK: only one Animal base via virtual inheritance
}
Output
Living...
Mark intermediate bases with virtual
to ensure only one shared base instance exists. This prevents ambiguity and saves memory.
Try it yourself
- Remove
virtual
and observe compilation errors or ambiguity; then restore it. - Add a member to
Animal
and access it fromBat
to confirm a single shared base.
🔹 Constructors, Destructors, and Initialization Order
Construction happens base‑first, then members, then the derived class body. Destruction reverses the order. Always initialize base classes in the derived class initializer list for clarity and correctness in Inheritance in C++.
#include <iostream>
using namespace std;
class Base {
public:
Base() { cout << "Base()\n"; }
~Base() { cout << "~Base()\n"; }
};
class Derived : public Base {
public:
Derived() { cout << "Derived()\n"; }
~Derived() { cout << "~Derived()\n"; }
};
int main() {
Derived d;
}
Output
Base()
Derived()
~Derived()
~Base()
If the base requires parameters, pass them from the derived constructor’s initializer list: Derived() : Base(args) {}
.
Try it yourself
- Add constructor parameters to
Base
and forward them fromDerived
using an initializer list. - Print messages in constructors/destructors to visualize exact order.
🔹 Protected Members and Derived Access
protected
members are hidden from outside code but accessible to derived classes. Use them sparingly when children must interact with base internals; otherwise favor private
with clear public/protected hooks.
#include <iostream>
using namespace std;
class Vehicle {
protected:
int speed{0};
void setSpeed(int s) { speed = s < 0 ? 0 : s; }
public:
int getSpeed() const { return speed; }
};
class Car : public Vehicle {
public:
void turbo() { setSpeed(speed + 50); } // allowed: protected in base
};
int main() {
Car c;
c.turbo();
cout << c.getSpeed() << "\n";
}
Output
50
Try it yourself
- Create
Truck
that caps speed at 120 usingsetSpeed
inside a custom method. - Demonstrate that outside code cannot access
speed
directly.
🔹 using, override, and final
The using Base::Base;
declaration can inherit constructors from a base (use with care). Use override
to ensure correct overriding and final
to prevent further overrides or inheritance on a class or method.
#include <iostream>
using namespace std;
class Base {
public:
virtual void run() { /* ... */ }
};
class Child : public Base {
public:
void run() override { /* child behavior */ } // checked by compiler
};
class Leaf final : public Child {
public:
// class Leaf cannot be derived further (final)
void run() override { /* final leaf behavior */ }
};
Try it yourself
- Mark a method
final
in a mid‑level class and try overriding it below—see the compiler error. - Use
using Base::Base;
to inherit base constructors and construct the child with a base‑style argument.
🔹 Mini Project: Employees with Polymorphic Pay
Combine Inheritance in C++ and polymorphism: compute pay differently for hourly versus salaried employees through a common interface, enabling clean, extensible code.
#include <iomanip>
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class Employee {
public:
virtual ~Employee() = default;
virtual double pay() const = 0;
virtual const char* kind() const = 0;
};
class Hourly : public Employee {
double rate;
double hours;
public:
Hourly(double r, double h) : rate(r), hours(h) {}
double pay() const override { return rate * hours; }
const char* kind() const override { return "Hourly"; }
};
class Salaried : public Employee {
double monthly;
public:
explicit Salaried(double m) : monthly(m) {}
double pay() const override { return monthly; }
const char* kind() const override { return "Salaried"; }
};
int main() {
vector<unique_ptr<Employee>> staff;
staff.push_back(make_unique<Hourly>(20.0, 160)); // 20/hr * 160h
staff.push_back(make_unique<Salaried>(5000.0)); // monthly fixed
cout << fixed << setprecision(2);
for (const auto& e : staff) {
cout << e->kind() <<
" pay: $" << e->pay() << "\n";
}
}
Output
Hourly pay: $3200.00
Salaried pay: $5000.00
Try it yourself
- Add a
Commissioned
employee with base salary plus percentage of sales. - Extend the program to sum total payroll using a loop over
Employee
pointers.
🔹 Best Practices for Inheritance in C++
- Use inheritance only for true “is‑a” relationships; otherwise prefer composition.
- Keep base class interfaces small, stable, and meaningful.
- Mark virtual methods with
override
in derived classes and provide avirtual
base destructor. - Avoid deep, complicated hierarchies; favor simple, testable layers.
- Use virtual inheritance only when needed (e.g., to resolve diamond problems).
🔹 Common Pitfalls to Avoid
- Using inheritance to reuse code when the relationship isn’t “is‑a” (leads to brittle designs).
- Forgetting a virtual destructor in polymorphic bases (can leak resources).
- Overriding without
override
(typos silently create new methods). - Exposing base internals via
protected
when a clear public hook would be safer. - Unnecessary multiple inheritance causing ambiguities; consider interfaces or composition.
🔹 FAQs: Inheritance in C++
Q1. What is the difference between public, protected, and private inheritance?
Public inheritance preserves the base’s public/protected visibility, protected turns them protected, and private turns them private. Public is the normal choice for “is‑a” models.
Q2. When should inheritance be used instead of composition?
Use inheritance for a true “is‑a” relationship (e.g., a Circle is a Shape). Use composition for “has‑a” (e.g., a Car has an Engine).
Q3. Why is a virtual destructor important?
In polymorphic bases, deleting via a base pointer must call the derived destructor; marking the base destructor virtual
guarantees proper cleanup.
Q4. What does override
do?
It asks the compiler to confirm the method really overrides a virtual base method, catching errors early (like mismatched signatures).
Q5. What is virtual inheritance?
It ensures there’s only one shared instance of a base when inherited along multiple paths (solves the diamond problem).
Q6. Can constructors be inherited?
Yes, with using Base::Base;
, but use carefully and still ensure derived class invariants are enforced.
🔹 Wrapping Up
Inheritance in C++ helps model natural hierarchies, reuse behavior, and enable polymorphism. Stick to true “is‑a” relationships, keep base interfaces clean, and use virtual
, override
, and virtual inheritance thoughtfully. Practice the “Try it yourself” tasks to strengthen understanding and confidence.