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
- 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.