C++ - Inheritance & Polymorphism

Overview

Estimated time: 60–80 minutes

Design hierarchies with base and derived classes. Use virtual functions for dynamic dispatch, prevent slicing, and apply override/final safely.

Learning Objectives

  • Declare base and derived classes; understand public/protected/private inheritance.
  • Use virtual functions, override, final, and virtual destructors.
  • Create abstract interfaces with pure virtual functions and avoid object slicing.

Prerequisites

Virtual functions and override

#include <iostream>
#include <memory>
struct Shape {
  virtual ~Shape() = default;                 // virtual destructor for polymorphic base
  virtual double area() const = 0;            // pure virtual: abstract interface
};
struct Rect : Shape {
  double w{0}, h{0};
  Rect(double w, double h): w{w}, h{h} {}
  double area() const override { return w*h; }
};
struct Circle : Shape {
  double r{0};
  explicit Circle(double r): r{r} {}
  double area() const override { return 3.14159 * r * r; }
};
int main(){
  std::unique_ptr<Shape> s1 = std::make_unique<Rect>(3,4);
  std::unique_ptr<Shape> s2 = std::make_unique<Circle>(2);
  std::cout << s1->area() << "\n"; // 12
  std::cout << s2->area() << "\n"; // ~12.566
}

Expected Output (example): 12 12.5664 (approx)

Prevent slicing

#include <vector>
#include <memory>
#include <iostream>
struct Base { virtual ~Base() = default; virtual void f() const { std::cout << "Base\n"; } };
struct Derived : Base { void f() const override { std::cout << "Derived\n"; } };
int main(){
  std::vector<std::unique_ptr<Base>> items;
  items.push_back(std::make_unique<Derived>()); // store polymorphically; avoids slicing
  for (auto& p : items) p->f();
}

Expected Output: Derived

final and protected constructors

struct NonInheritable final { };
struct BaseOnly {
protected:
  BaseOnly() = default; // force construction via derived
};
struct Derived2 : BaseOnly { using BaseOnly::BaseOnly; };

Beginner Boosters

#include 
#include 
#include 
struct Base{ virtual ~Base()=default; virtual void hello() const { std::cout << "Base\n"; } };
struct Sub: Base{ void hello() const override { std::cout << "Sub\n"; } };
int main(){
  std::vector> xs;
  xs.push_back(std::make_unique());
  xs.push_back(std::make_unique());
  for (auto& p: xs) p->hello();
}

Expected Output: Sub Base

Common Pitfalls

  • Missing virtual destructor in a polymorphic base leading to undefined behavior on delete through base pointer.
  • Object slicing by assigning a Derived to a Base by value; prefer references/pointers to base.
  • Overriding without override can silently create a new overload; always use it.

Checks for Understanding

  1. Why should polymorphic bases have virtual destructors?
  2. What causes object slicing, and how do you avoid it?
Show answers
  1. To ensure the derived destructor runs when deleting via a base pointer.
  2. Copying a derived object into a base object by value; use references/smart pointers to base.

Exercises

  1. Create a Shape hierarchy with triangle and ellipse; implement area() and test via base pointers.
  2. Demonstrate slicing with Base b = Derived{}; fix the code using std::unique_ptr.