Sobes.tech
Middle

How does the virtual mechanism work in C++?

sobes.tech AI

Answer from AI

The mechanism of virtual functions in C++ is implemented using pointers to functions (the virtual function table, or VMT - Virtual Method Table) and a pointer to this table (vptr - virtual pointer), which is added to each object of a class with virtual functions or inherited from such a class.

When a function is declared as virtual, the compiler creates a VMT for that class. This table contains pointers to the actual implementations of the virtual functions for that class.

Each object of the class (or derived from it) has a hidden pointer vptr, which points to the VMT of the corresponding object type.

When a virtual function is called through a pointer or reference to the base class, the compiler generates code that uses the vptr of the object to find the address of the required function in the VMT and calls it. This is called dynamic dispatch (or late binding), as the decision about which function will be called is made at runtime, not at compile time.

Example:

#include <iostream>

class Base {
public:
    virtual void greet() { // Virtual function
        std::cout << "Hello from Base" << std::endl;
    }
};

class Derived : public Base {
public:
    void greet() override { // Override virtual function
        std::cout << "Hello from Derived" << std::endl;
    }
};

int main() {
    Base* ptr = new Derived(); // Pointer to base class points to derived class object
    ptr->greet(); // Call to virtual function through pointer
    delete ptr;
    return 0;
}

In this example, even though ptr has the type Base*, the actual implementation of greet from the Derived class will be called because the vptr of the Derived object, which ptr points to, refers to the VMT of the Derived class.

Non-virtual functions are called through static dispatch (early binding), where the function address is determined at compile time based on the pointer or reference type.

Key aspects:

  • Runtime polymorphism: Virtual functions allow calling the correct version of a function depending on the actual object type, not the pointer/reference type.
  • Overhead: Virtual functions add a small overhead for object creation (for vptr) and each call (for lookup in the VMT).
  • override: The override keyword in C++11 (and later) helps prevent errors when overriding virtual functions, indicating that the function in the derived class should override a function from the base class.
  • Virtual destructor: It is important to declare the destructor of the base class as virtual if objects of derived classes are planned to be deleted through a pointer to the base class, to avoid memory leaks.
#include <iostream>

class Base {
public:
    virtual ~Base() { // Virtual destructor
        std::cout << "Base destructor" << std::endl;
    }
};

class Derived : public Base {
public:
    ~Derived() override { // Override destructor
        std::cout << "Derived destructor" << std::endl;
    }
};

int main() {
    Base* ptr = new Derived();
    delete ptr; // Correctly calls Derived destructor and then Base destructor
    return 0;
}