Chapter 27: Virtual Functions

Introduction

A virtual function is a special type of member function that, when called, resolves to the most-derived version of the function for the actual type of the object being referenced or pointed to. This enables runtime polymorphism, allowing derived classes to override base class functions.

Basic Example

class Base
{
public:
    virtual std::string_view getName() const { return "Base"; } // note addition of virtual keyword
};

class Derived: public Base
{
public:
    virtual std::string_view getName() const { return "Derived"; }
};

int main()
{
    Derived derived {};
    Base& rBase{ derived };
    std::cout << "rBase is a " << rBase.getName() << '\n'; // rbase is a Derived
}

Polymorphism

Using override

Using the override specifier ensures that the function is indeed overriding a base class function.

class A {
public:
    virtual std::string_view getName() const { return "A"; }
};

class B : public A {
public:
    std::string_view getName() const override { return "B"; } // Correct override
};

Using final

The final specifier prevents further overriding of a virtual function or inheritance from a class.

class A {
public:
    virtual std::string_view getName() const { return "A"; }
};

class B : public A {
public:
    std::string_view getName() const override final { return "B"; } // Cannot be overridden further
};

class C : public B {
public:
    std::string_view getName() const override { return "C"; } // Compile error: B::getName() is final
};

Virtual Destructors

Whenever you are dealing with inheritance, you should make any explicit destructors virtual.

class Base {
public:
    virtual ~Base() = default; // Virtual destructor
};

Pure Virtual Functions

A pure virtual function is a virtual function that is declared by using the = 0 syntax. Classes with pure virtual functions become abstract base classes and cannot be instantiated.

class Animal {
public:
    virtual std::string_view speak() const = 0; // pure virtual function
};

class Dog : public Animal {
public:
    std::string_view speak() const override { return "Woof"; }
};

Defining Pure Virtual Functions

Pure virtual functions can still have a definition.

std::string_view Animal::speak() const
{
    return "buzz";
}

Interface Classes

An interface class is a class that has no member variables, and all of its functions are pure virtual.

class Interface {
public:
    virtual void func1() = 0;
    virtual void func2() = 0;
};

Virtual Inheritance

To share a base class in multiple inheritance, use virtual inheritance.

class PoweredDevice {};
class Scanner : virtual public PoweredDevice {};
class Printer : virtual public PoweredDevice {};
class Copier : public Scanner, public Printer {};

Downcasting with dynamic_cast

If a dynamic_cast fails, the result of the conversion will be a null pointer.

Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b); // use dynamic cast to convert Base pointer into Derived pointer

Note that there are several cases where downcasting using dynamic_cast will not work:

Using static_cast

if (b->getClassID() == ClassID::derived)
{
    Derived* d{ static_cast<Derived*>(b) }; // safe downcast
}

Virtual Table (vtable)

Operator Overloading and Virtual Functions

friend std::ostream& operator<<(std::ostream& out, const Base& b)
{
    // Delegate printing responsibility for printing to virtual member function print()
    return b.print(out);
}

// We'll rely on member function print() to do the actual printing
// Because print() is a normal member function, it can be virtualized
virtual std::ostream& print(std::ostream& out) const
{
    out << "Base";
    return out;
}

Virtual functions enable runtime polymorphism, allowing derived classes to override base class functions. Understanding how to use virtual, override, final, and pure virtual functions is crucial for designing robust and maintainable object-oriented systems.