Virtual functions
The inheritance lesson ended with a puzzle: a Circle used through a Shape& ran Shape's function. Mark the function virtual, and C++ instead looks at what the object really is when the program runs. One piece of code can then work with many types. That's polymorphism.
virtual and override
#include <iostream>
#include <string>
using namespace std;
class Animal {
public:
virtual string sound() const { return "..."; }
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
string sound() const override { return "Woof"; }
};
class Cat : public Animal {
public:
string sound() const override { return "Meow"; }
};
void speak(const Animal& a) {
cout << a.sound() << endl; // which sound()? decided by the real object
}
int main() {
Dog d;
Cat c;
Animal a;
speak(d);
speak(c);
speak(a);
return 0;
}Woof
Meow
...speak knows nothing about dogs or cats, yet each animal makes its own sound, because sound is virtual. Through a reference or pointer to the base, a virtual call runs the version belonging to the object's actual class.
override tells the compiler you mean to replace a virtual function. If the signatures don't match (you forgot the const, say), it's an error instead of a silent new function:
class Dog : public Animal {
string sound() override { return "Woof"; } // error: marked 'override', but does not override
};Abstract classes
#include <iostream>
#include <string>
using namespace std;
class Shape {
public:
virtual double area() const = 0; // pure virtual: no body here
virtual string name() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
double r;
public:
Circle(double r) : r(r) {}
double area() const override { return 3.14159 * r * r; }
string name() const override { return "circle"; }
};
class Rect : public Shape {
double w, h;
public:
Rect(double w, double h) : w(w), h(h) {}
double area() const override { return w * h; }
string name() const override { return "rectangle"; }
};
int main() {
Circle c(1);
Rect r(3, 4);
Rect square(2, 2);
Shape* shapes[] = {&c, &r, &square};
double total = 0;
for (Shape* s : shapes) {
cout << s->name() << ": " << s->area() << endl;
total += s->area();
}
cout << "total: " << total << endl;
return 0;
}circle: 3.14159
rectangle: 12
rectangle: 4
total: 19.1416= 0 makes a function pure virtual: Shape declares that every shape has an area but doesn't say how. A class with a pure virtual function is abstract, so you can't create a plain Shape (Shape s; doesn't compile). It exists to be derived from, and every concrete shape must supply its own area and name.
The loop is the payoff. It handles circles and rectangles alike, and a new Triangle class would work in it without changing a line.
Virtual destructors
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() { cout << "~Base" << endl; }
};
class Derived : public Base {
public:
~Derived() override { cout << "~Derived" << endl; }
};
int main() {
Base* p = new Derived();
delete p; // deleting a Derived through a Base pointer
return 0;
}~Derived
~BaseWhen an object is deleted through a pointer to its base, the destructor must be virtual so the derived part is cleaned up too. Without virtual, delete p would run only ~Base: the behaviour is undefined, and any memory or file the derived part owned would leak.
The rule: a class with any virtual function should have a virtual destructor. virtual ~Animal() = default; costs one line.
How it works
Each class with virtual functions has a hidden table of function pointers, and each object carries a pointer to its class's table. A virtual call looks the function up in that table, which is why it can depend on the real type. It costs one extra lookup per call, and nothing for functions that aren't virtual.
Add final to a class (class Dog final : public Animal) or a function to stop further overriding.