Inheritance & Polymorphism
Deriving a class
A derived class declares a base class after a colon. It inherits the base's members and can add its own state and behavior.
Base and derived
class Shape {
public:
void setColor(const std::string &color) { this->color = color; }
protected: // visible to derived classes
std::string color = "black";
};
class Rectangle : public Shape { // Rectangle is-a Shape
public:
double area() const { return width * height; }
private:
double width = 1.0, height = 1.0;
};
public Shape means "is-a": a Rectangle can be used anywhere a
Shape is expected. The protected members are invisible to the outside
world but visible inside derived classes.
What is inherited
The derived class inherits all public and protected members of the base — its methods become available on the derived object:
Rectangle r;
r.setColor("red"); // inherited from Shape
std::cout << r.area(); // Rectangle's own method
What is not inherited: constructors, the destructor, and private members.
Also, if the base and derived methods share a name, the derived version hides the base's — declare
in the derived class with using Base::name; when you need both.
Virtual functions & polymorphism
The real power of inheritance comes from virtual functions. Mark a base method
virtual; each derived class can provide its own implementation. When you call through
a base pointer or reference, the derived version runs — the call is resolved at run time
based on the object's actual type.
class Shape {
public:
virtual double area() const { return 0.0; } // overridable
};
class Circle : public Shape {
public:
double area() const override { // override: safer
return 3.14159 * radius * radius;
}
private:
double radius = 1.0;
};
void printArea(const Shape &s) { // accepts any Shape
std::cout << s.area() << "\n"; // dispatch to the real type
}
override
Write override after every overriding method. It asks the compiler to verify the base
really has a matching virtual — catching typos and signature mismatches at compile time. A base
method should be virtual only when derived classes are expected to replace it; making
everything virtual has a (small) cost and weakens the interface contract.
Pure virtual & abstract
A virtual function can declare no implementation: = 0 makes it pure virtual.
A class with a pure virtual cannot be instantiated — it is an abstract interface
that only shapes the derived classes:
class Shape {
public:
virtual double area() const = 0; // pure: no body here
virtual ~Shape() = default; // virtual destructor (essential!)
};
// Shape s; // error: Shape is abstract
Circle c;
printArea(c); // works through the interface
Any class meant to be deleted through a base pointer must have a virtual destructor — otherwise only the base part is destroyed, leaking derived resources.
An abstract base class (Shape) with two concrete derived classes.
Slicing & references
Polymorphism works only through pointers and references. Copying a derived object into a base value slices off the derived part:
Circle c;
Shape s = c; // SLICING: only the Shape part is copied; area() is Shape's
Shape &ref = c; // OK: reference keeps the real object
Shape *ptr = &c; // OK: pointer keeps the real object
Slicing is silent data loss. When you need polymorphic collections, store smart pointers to the base type — the memory lesson shows how.
final & best practices
A class or method marked final cannot be derived from or overridden — a deliberate
lock that helps readers and the compiler:
class Circle final : public Shape { // no class may derive from Circle
double area() const override final { return 3.14159; }
};
Inheritance guidance in one line: prefer composition (a class contains another class) over inheritance unless a genuine "is-a" relationship with polymorphic behavior exists. Deep hierarchies (more than two or three levels) become hard to reason about.
Practice
- Build a small
Animalhierarchy: base withvirtual speak(), derivedDogandCatoverriding it. - Store cats and dogs in a
std::vector<Animal *>and callspeak()on each — the virtual dispatch must produce the right sound. - Add a pure virtual
name()toAnimaland implement it in both derived classes. - Demonstrate slicing by copying a
Doginto anAnimalvalue and showing the wrongspeak()runs. - Explain why the base destructor must be virtual when destruction happens through a base pointer.