virtual, override and polymorphism
Through a reference to a base class, code can work with any derived class. But an ordinary method called through such a reference comes from the base class, even when the derived class declares its own:
#include <iostream>
struct Shape {
double area() const { return 0; }
};
struct Square : public Shape {
double side = 2;
double area() const { return side * side; }
};
int main() {
Square sq;
const Shape& s = sq;
std::cout << sq.area() << ' ' << s.area() << '\n'; // 4 0
}The compiler picks area by the type of the reference s, which is Shape, not by the object it refers to.
Virtual functions
A method declared in the base class with the word virtual becomes a virtual function. A call through a reference or a pointer picks its version by the object's real type, while the program runs. This is polymorphism: the same call s.area() works differently for different shapes.
struct Shape {
virtual double area() const { return 0; }
};
struct Square : public Shape {
double side = 2;
double area() const override { return side * side; }
};With this Shape, the same main prints 4 4. The word override asks the compiler to check that the method overrides a virtual function of the base class. The name, the parameter types and const must all match, or the program does not compile.
Picking by the real type works only through a reference or a pointer. A Shape copy sliced from a Square object is an ordinary Shape object, and calling area on it runs Shape::area.
Abstract classes
A "shape in general" has no sensible area. Then the virtual function is declared without a body, with = 0: virtual double area() const = 0;. A class with such a function is abstract: no object of it can be created, but references and pointers to it can. A derived class that overrides all such functions is no longer abstract.
A base class with virtual functions also gets a virtual destructor: virtual ~Shape() {}. Then a derived object destroyed through a pointer to the base class is destroyed completely.
dynamic_cast
Sometimes code needs to find out what object a base-class pointer actually points to. dynamic_cast<Square*>(p) checks this at run time. If p points to a Square or an object derived from it, the result is a Square* pointer; otherwise, it is nullptr. This check needs Shape to have at least one virtual function; otherwise, the code does not compile.
Square sq;
Shape* p = &sq;
Square* q = dynamic_cast<Square*>(p);
if (q != nullptr) {
std::cout << q->side << '\n'; // 2
}Common mistake
The derived class is missing const:
struct Shape {
virtual double area() const { return 0; }
};
struct Square : public Shape {
double side = 2;
double area() { return side * side; } // ✗
};warning: 'Square::area' hides overloaded virtual function [-Woverloaded-virtual]Without const, it is a different function: it does not override Shape::area but hides it. The program builds, but the call through a reference to Shape still returns 0. With override, the compiler would catch this mistake, so write double area() const override.