C++ Polymorphism and Virtual Functions
Polymorphism means "many forms": one piece of code works with objects of different classes, and each object behaves in its own way. In C++ it works through virtual functions called via a pointer or reference to a base class.
virtual and override
#include <iostream>
#include <string>
#include <vector>
class Animal {
protected:
std::string name;
public:
explicit Animal(const std::string& n) : name(n) {}
virtual ~Animal() = default;
virtual std::string sound() const { return "..."; }
void speak() const { std::cout << name << " says " << sound() << "\n"; }
};
class Dog : public Animal {
public:
explicit Dog(const std::string& n) : Animal(n) {}
std::string sound() const override { return "Woof"; }
};
class Cat : public Animal {
public:
explicit Cat(const std::string& n) : Animal(n) {}
std::string sound() const override { return "Meow"; }
};
int main() {
Dog d("Bruno");
Cat c("Kitty");
Animal a("Generic");
std::vector<const Animal*> zoo = {&d, &c, &a};
for (const Animal* p : zoo) p->speak();
return 0;
}Bruno says Woof Kitty says Meow Generic says ...
speak belongs to Animal, yet each call prints the right sound: because sound is virtual, the version of the object's real class is chosen while the program runs. Without virtual, every animal would say "...".
override asks the compiler to check that the method really replaces a virtual method of the base class. If the name or the parameters do not match, you get an error instead of a silent new method. Always write it.
Pure virtual functions and abstract classes
A virtual function with = 0 instead of a body is pure virtual. A class with one is abstract: no objects of it can be made, only of derived classes that override every pure virtual function. This describes an interface: "every shape must have an area".
#include <iostream>
#include <memory>
#include <vector>
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
virtual const char* name() const = 0;
};
class Rectangle : public Shape {
double w, h;
public:
Rectangle(double w, double h) : w(w), h(h) {}
double area() const override { return w * h; }
const char* name() const override { return "rectangle"; }
};
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"; }
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Rectangle>(4, 5));
shapes.push_back(std::make_unique<Circle>(1));
double total = 0;
for (const auto& s : shapes) {
std::cout << s->name() << ": " << s->area() << "\n";
total += s->area();
}
std::cout << "total: " << total << "\n";
return 0;
}rectangle: 20 circle: 3.14159 total: 23.1416
std::unique_ptr (see Smart Pointers) owns each shape and deletes it automatically.
Virtual destructors
A class meant to be used through base pointers must have a virtual destructor (virtual ~Shape() = default;). Otherwise, deleting a derived object through a base pointer runs only the base destructor, and the derived part is never cleaned up.
Why it matters
Polymorphism lets you add new kinds of objects without changing the code that uses them: a Triangle class can join the shapes vector, and the loop works unchanged. It is the foundation of plug-ins, game entities and GUI widgets.
Practice exercises
- Add a
Cowclass that says "Moo" and add it to the zoo. - Add a
Triangleshape with a base and a height, without changing the loop. - Remove
virtualfromsoundand explain the new output.