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    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 overridecan silently create a new overload; always use it.
Checks for Understanding
- Why should polymorphic bases have virtual destructors?
- What causes object slicing, and how do you avoid it?
Show answers
- To ensure the derived destructor runs when deleting via a base pointer.
- Copying a derived object into a base object by value; use references/smart pointers to base.
Exercises
- Create a Shape hierarchy with triangle and ellipse; implement area() and test via base pointers.
- Demonstrate slicing with Base b = Derived{}; fix the code using std::unique_ptr.