C++ Polymorphism and Virtual Functions

Polymorphism means one call, many behaviors. In C++ it needs virtual functions and a base pointer or reference.

#include <iostream>
#include <memory>
#include <numbers>
#include <vector>

class Shape {
public:
    virtual ~Shape() = default;                    // a virtual destructor
    virtual double area() const { return 0; }
};

class Circle : public Shape {
public:
    explicit Circle(double r) : r(r) {}
    double area() const override { return std::numbers::pi * r * r; }
private:
    double r;
};

class Rectangle : public Shape {
public:
    Rectangle(double w, double h) : w(w), h(h) {}
    double area() const override { return w * h; }
private:
    double w, h;
};

int main() {
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Circle>(2));
    shapes.push_back(std::make_unique<Rectangle>(3, 4));

    double total = 0;
    for (const auto& s : shapes) {
        std::cout << s->area() << '\n';           // the real object decides
        total += s->area();
    }
    std::cout << "Total: " << total << '\n';
}
Output

12.5664
12
Total: 24.5664

virtual, override and final

  • virtual in the base: the function can be replaced, and the call is decided when the program runs.
  • override in the derived class: the compiler checks that you really replace a base function.
  • final: no more overriding (or no more derived classes).
struct Base {
    virtual void run() const {}
};

struct Derived : Base {
    void run() override {}           // missing const: this is not an override, the compiler tells you
};

int main() { }
Output

Override.cpp:6:10: error: 'void Derived::run()' marked 'override', but does not override
6 | void run() override {} // missing const: this is not an override, the compiler tells you
| ^~~
Override.cpp:2:18: warning: 'virtual void Base::run() const' was hidden [-Woverloaded-virtual=]
2 | virtual void run() const {}
| ^~~
Override.cpp:6:10: note: by 'void Derived::run()'
6 | void run() override {} // missing const: this is not an override, the compiler tells you
| ^~~

Without virtual: no polymorphism

#include <iostream>

struct Animal { void sound() const { std::cout << "...\n"; } };
struct Dog : Animal { void sound() const { std::cout << "Woof\n"; } };

int main() {
    Dog d;
    Animal& a = d;
    a.sound();                       // calls Animal::sound: the function is not virtual
}
Output

…

Object slicing

If you copy a derived object into a base object (not a pointer or reference), the derived part is cut off:

#include <iostream>

struct Base { virtual const char* name() const { return "Base"; } };
struct Derived : Base { const char* name() const override { return "Derived"; } };

void by_value(Base b) { std::cout << b.name() << '\n'; }
void by_reference(const Base& b) { std::cout << b.name() << '\n'; }

int main() {
    Derived d;
    by_value(d);              // sliced
    by_reference(d);          // polymorphic
}
Output

Base
Derived

The virtual destructor

If you delete a derived object through a base pointer and the base destructor is not virtual, the derived destructor does not run (undefined behavior). A class with virtual functions should have a virtual destructor.

Note: Always write override. It finds many mistakes at compile time. Next: C++ Abstract Classes and Interfaces.

Congratulations! Chapter Finished. Learn more about the similar topics:
Exercises & Assignments
No Content Found.
Interview Questions & Answers
No Content Found.