Runtime polymorphism through virtual functions is how C++ code handles plugin-style designs, GUI widgets and many frameworks.
class Circle : public Shape makes Circle a Shape: it inherits Shape's members, and a Circle can be used wherever a Shape& or Shape* is expected.
A member function declared virtual in the base is dispatched at run time on the object's real type. Calling s->area() through a Shape* that points to a Circle runs Circle::area. Mark each overriding function override: the compiler then errors if the signature doesn't actually match a base virtual, which catches typos and mismatched const. A function declared = 0 is pure virtual; a class with one is abstract and can't be instantiated, which makes it an interface. final stops further overriding or derivation.
Three rules that matter in practice:
- Give a polymorphic base class a virtual destructor (
virtual ~Shape() = default;). Otherwise deleting a derived object through a base pointer, which is exactly whatstd::unique_ptr<Shape>does, is undefined behaviour. - Polymorphism only works through references and pointers. Copying a
Circleinto aShapevariable slices it: only the base part is copied and the virtual calls go toShape. Hold polymorphic objects asstd::unique_ptr<Base>in containers. - Don't call virtual functions from constructors or destructors expecting derived behaviour; during construction the object is still only a base.
Each polymorphic object carries a hidden pointer to its class's vtable, and a virtual call is an indirect call that's hard to inline. That cost is small but real in hot loops.
Use inheritance for "is-a" relationships with polymorphic behaviour. To reuse code, prefer composition (a member) over inheritance.
Example
#include <iostream>
#include <memory>
#include <vector>
class Shape {
public:
virtual ~Shape() = default; // needed to delete through Shape*
virtual double area() const = 0; // pure virtual: Shape is abstract
virtual const char* name() const { return "shape"; }
};
class Circle : public Shape {
double r_;
public:
explicit Circle(double r) : r_{r} {}
double area() const override { return 3.14159 * r_ * r_; }
const char* name() const override { return "circle"; }
};
class Square final : public Shape {
double side_;
public:
explicit Square(double s) : side_{s} {}
double area() const override { return side_ * side_; } // name() inherited
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(1.0));
shapes.push_back(std::make_unique<Square>(2.0));
for (const auto& s : shapes)
std::cout << s->name() << ' ' << s->area() << '\n'; // circle 3.14159, shape 4
}Remember
virtual+overridegives runtime dispatch; always writeoverride- Polymorphic bases need a virtual destructor
- Use polymorphic objects through references or pointers; copying slices them
Watch out for
- Storing derived objects in a
std::vector<Base>— they are sliced to Base. Usestd::vector<std::unique_ptr<Base>>.