Last 30 Days
No notifications
Inheritance lets a class extend another. Polymorphism lets you call the right behaviour through a base-class pointer/reference.
class Animal {
public:
virtual void speak() const { cout << "..."; } // virtual = overridable
virtual ~Animal() = default; // virtual destructor!
};class Dog : public Animal {
public:
void speak() const override { cout << "Woof!"; }
};
class Cat : public Animal {
public:
void speak() const override { cout << "Meow."; }
};
void greet(const Animal& a) { a.speak(); } // polymorphic call
int main() {
Dog d; Cat c;
greet(d); // Woof!
greet(c); // Meow.
}
| Need | Keyword |
| Mark a method as overridable | virtual |
| Confirm you're overriding | override |
| Force derived classes to provide | = 0 (pure virtual) |
| Forbid further overriding | final |
class Dog : public Animal { /* ... */ }; // is-a (most common)
class Box : private Stack { /* ... */ }; // implemented-in-terms-of (rare)A class with at least one pure virtual method:
class Shape {
public:
virtual double area() const = 0; // must be overridden
virtual ~Shape() = default;
};
// Shape s; // ❌ can't instantiate abstract classInheritance is C++'s mechanism for is-a relationships and code reuse. Polymorphism is what makes it powerful — the same call site dispatches to different code depending on the actual object.
class Vehicle {
protected:
int wheels;
public:
Vehicle(int w) : wheels(w) {}
int getWheels() const { return wheels; }
};class Car : public Vehicle {
string brand;
public:
Car(string b) : Vehicle(4), brand(std::move(b)) {}
const string& getBrand() const { return brand; }
};
Car automatically has wheels and getWheels() because it derives from Vehicle.
| Inherit as | public members | protected members | private members |
public | stay public | stay protected | inaccessible |
protected | become protected | stay protected | inaccessible |
private | become private | become private | inaccessible |
99% of the time you want public inheritance — that's the is-a relationship.
Constructor: Base then Derived
Destructor: Derived then BaseYou must initialise the base class explicitly when its constructor takes args:
class Dog : public Animal {
public:
Dog(string name) : Animal(std::move(name)) {} // call base ctor
};Without virtual, calling a method through a base reference uses the base version:
struct Base { void hello() { cout << "Base"; } };
struct Derived : Base { void hello() { cout << "Derived"; } };Derived d;
Base& r = d;
r.hello(); // prints "Base" — static dispatch
With virtual, the actual object's method runs:
struct Base { virtual void hello() { cout << "Base"; } };
struct Derived : Base { void hello() override { cout << "Derived"; } };Derived d;
Base& r = d;
r.hello(); // prints "Derived" — dynamic dispatch
This is runtime polymorphism, the engine of OOP design patterns (Strategy, Observer, Visitor, …).
Each polymorphic class has a hidden pointer to a vtable — a function pointer table. r.hello() looks up hello in the vtable. Cost: one extra indirection.
override and finalclass Dog : public Animal {
public:
void speak() const override { cout << "Woof"; } // compile-time check
void run() final { /* can't be overridden further */ }
};override tells the compiler "this is meant to override a base virtual" — typo? compile error.final forbids further overriding (or, on a class, forbids derivation).override on overrides. Catches bugs.class Shape {
public:
virtual double area() const = 0;
virtual double perimeter() const = 0;
virtual ~Shape() = default;
};= 0 makes a method pure virtual. Any class with at least one pure virtual is abstract — you can't instantiate it; you can only derive and implement.
class Circle : public Shape {
double r;
public:
Circle(double r) : r(r) {}
double area() const override { return 3.14159 * r * r; }
double perimeter() const override { return 2 * 3.14159 * r; }
};> Interfaces in C++ are just abstract classes with all-pure-virtual methods. No special keyword needed.
If you ever delete a derived object through a base pointer, the base's destructor MUST be virtual — otherwise only the base destructor runs, leaking the derived part.
class Animal { public: virtual ~Animal() = default; }; // ✅
class Dog : public Animal { vector<string> tricks; };Animal* a = new Dog{};
delete a; // both ~Dog (cleans tricks) and ~Animal run — correct
Rule: any class meant to be inherited from publicly should have a virtual destructor.
Pass a derived object by value to a base parameter and the derived part is *sliced* off:
void describe(Animal a) { a.speak(); } // by VALUE — slicesDog d;
describe(d); // calls Animal::speak — only the Animal sub-object survived
Always pass polymorphic types by reference or pointer (const Animal&, Animal*).
class Logger : public Component {
public:
void update() override {
Component::update(); // call base implementation first
log("updated");
}
};C++ allows it; mostly you'll see it for interfaces (mixing in pure-virtual classes):
class Printable { public: virtual void print() const = 0; virtual ~Printable() = default; };
class Serialisable { public: virtual string toJson() const = 0; virtual ~Serialisable() = default; };class Profile : public Printable, public Serialisable { /* ... */ };
Inheriting from multiple non-interface classes can lead to the diamond problem — solvable with virtual inheritance, but the pain often outweighs the gain. Prefer composition.
> Prefer composition (has-a) over inheritance (is-a) unless you genuinely model an is-a relationship.
// Inheritance — Car IS-A Vehicle
class Car : public Vehicle { /* ... */ };// Composition — Car HAS-A Engine
class Car {
Engine engine; // owns an engine
Wheels wheels[4];
};
| Need | Code |
| Inherit publicly | class Dog : public Animal { ... }; |
| Initialise base | Dog() : Animal("dog") {} |
| Overridable method | virtual void f(); |
| Override in derived | void f() override; |
| Force impl in derived | virtual void f() = 0; (pure virtual) |
| Lock further override | void f() final; |
| Polymorphic delete | virtual ~Base() = default; |
| Avoid slicing | pass const T& or T* |
You now have the OOP toolkit. Up next: memory management & smart pointers — how to own those polymorphic objects safely.