Introduction
Polymorphism ("many forms") lets one piece of code work with objects of many classes. In C++ it comes from virtual functions called through base-class pointers or references.
Inheritance on its own reuses code. Polymorphism is what makes it powerful: a list of shapes, animals or employees can be handled by one loop, while each object behaves in its own way.
How it works
Virtual functions
#include <iostream>
using namespace std;
class Shape {
public:
virtual double area() const {
return 0;
}
virtual ~Shape() {}
};
class Circle : public Shape {
double r;
public:
Circle(double r) : r(r) {}
double area() const override {
return 3.14159 * r * r;
}
};
class Square : public Shape {
double side;
public:
Square(double s) : side(s) {}
double area() const override {
return side * side;
}
};
int main() {
Shape* shapes[3] = {new Circle(1), new Square(2), new Circle(0.5)};
double total = 0;
for (int i = 0; i < 3; i++) {
Shape* s = shapes[i];
total += s->area();
}
cout << "total area " << total << endl;
for (int i = 0; i < 3; i++) delete shapes[i];
return 0;
}shapes is an array of Shape *, but each pointer points to a Circle or a Square. Because area() is virtual, s->area() runs the version for the object's real class, decided while the program runs. Watch the frames: the same line calls Circle::area, then Square::area. Without virtual, every call would run Shape::area, chosen from the pointer's type alone.
overridein the derived class asks the compiler to check that you really override a virtual function (and did not misspell it, or change its parameters).- Each object of a class with virtual functions carries a hidden pointer, the vptr, to its class's table of functions. That is how the call finds the right version. StepVioz keeps it hidden in the memory view.
Syntax
class Base {
public:
virtual void act() const; // can be overridden
virtual ~Base() {} // virtual destructor
};
class Derived : public Base {
public:
void act() const override; // the compiler checks it
};
Example
#include <iostream>
using namespace std;
class Animal {
public:
virtual void speak() const {
cout << "..." << endl;
}
virtual ~Animal() {
cout << "~Animal" << endl;
}
};
class Dog : public Animal {
public:
void speak() const override {
cout << "Woof" << endl;
}
~Dog() {
cout << "~Dog" << endl;
}
};
int main() {
Animal* p = new Dog();
p->speak();
delete p;
return 0;
}delete p through an Animal * must run the derived destructor too, or the derived part's resources leak. That only happens if the base destructor is virtual. Here ~Dog and then ~Animal run, in that order. Rule: a class with any virtual function needs a virtual destructor.
Line by line
Animaldeclaresspeakand its destructor asvirtual.Dogoverridesspeakand marks it withoverride.Animal* p = new Dog();stores aDogbehind anAnimalpointer.p->speak()printsWoof, because the call is decided by the real object, aDog.delete pruns~Dogand then~Animal, because the base destructor is virtual.
Expected result
Woof
~Dog
~Animal
In the real world
Plug-in systems, device drivers and GUI event handlers are built on virtual functions: the framework calls a function through a base pointer, and your derived class supplies what happens. Unit-testing frameworks use the same idea to swap in test doubles.
Common mistakes
- Forgetting
virtualin the base class: the base version always runs. - Overriding with different parameters (
area(int)instead ofarea()): it is a different function.overridecatches this. - Copying a derived object into a base object (
Shape s = circle;) cuts off the derived part (slicing). Polymorphism needs pointers or references.
Best practices
- Mark every overriding function with
override. - Give every base class with a virtual function a virtual destructor.
- Handle polymorphic objects through pointers or references.
Summary
Virtual functions let a base pointer or reference call the version that belongs to the object's real class. override catches mistakes, slicing is avoided by never copying into a base object, and a polymorphic base class needs a virtual destructor.
Quick check
Key takeaways
- virtual makes the object's real class choose the function.
- Polymorphism works through pointers and references, never through copies.
- A class with virtual functions needs a virtual destructor.
Practice problems for this lesson
My notes
Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.