22.1.  virtual Pointers, virtual Tables

[ fromfile: vtable.xml id: vtable ]

Each class that contains methods (virtual functions) has a virtual jump table, or vtable, which is generated as part of the "lightweight" C++ execution environment. The vtable can be implemented in a number of ways, but the simplest implementation (which is often the fastest and most lightweight) contains a list of pointers to all methods of that class. Depending on the optimization policies, it may contain additional information to aid in debugging. The compiler substitutes function names with indirect (relative to the vtable list) references to method calls.

With this in mind, we define polymorphic type explicitly as a class that contains one or more methods, and thus, requires the use of a vtable. Each instance of a polymorphic type has a typeid, which can be quite naturally implemented as the address of the vtable for the class.

A vtable cannot be built for a class unless the method definitions for all overrides are fully defined and findable by the linker.

The typeid of an object is set after the object's constructor has executed. If there are base classes, the typeid for an object may be set multiple times, after each base class initialization.

We use the classes defined in Example 22.1 to demonstrate that calling a virtual function from a constructor or destructor can have unexpected consequences.

Example 22.1. src/derivation/typeid/vtable.h

[ . . . . ]
class Base {
 protected:
    int m_X, m_Y;
 public:
    Base();
    virtual ~Base();
    virtual void virtualFun() const;
};

class Derived : public Base {
    int m_Z;
 public:
    Derived();
    ~Derived();
    void virtualFun() const ;
};
[ . . . . ]

Example 22.2 shows what happens when a virtual function is called from a base class constructor or destructor.

Example 22.2. src/derivation/typeid/vtable.cpp

#include <QDebug>
#include <QString>
#include "vtable.h"

Base::Base() {
    m_X = 4;
    m_Y = 12;
    qDebug() << " Base::Base: " ;
    virtualFun();
}

Derived::Derived() {
    m_X = 5;
    m_Y = 13;
    m_Z = 22;
}

void Base::virtualFun() const {
    QString val=QString("[%1,%2]").arg(m_X).arg(m_Y);
    qDebug() << " VF: the opposite of Acid: " << val;
}

void Derived::virtualFun() const {
    QString val=QString("[%1,%2,%3]")
        .arg(m_X).arg(m_Y).arg(m_Z);
    qDebug() << " VF: add some treble: " ;
}

Base::~Base() {
    qDebug() << " ~Base() " ;
    virtualFun();
}

Derived::~Derived() {
    qDebug() << " ~Derived() " ;
}


int main() {
    Base *b = new Derived;  1 
    b->virtualFun();        2
    delete b;               3
}

1

Base::virtualFun() is called

2

calls Derived::virtualFun() using the vtable and runtime binding

3

Base::virtualFun() is called


In the output that follows, you can see that the derived virtualFun() does not get called from Base::Base() because the base class initializer is inside an object that is not yet a Derived instance.

 Base::Base:  
 VF: the opposite of Acid:  "[4,12]" 
 VF: add some treble:  
 ~Derived()  
 ~Base()  
 VF: the opposite of Acid:  "[5,13]" 

Calling virtual methods from destructors is also not recommended. In the previous output, you can see that the base virtualFun is always called from the base class constructors or destructor. Dynamic binding does not happen inside constructors or destructors. "From a constructor or destructor, virtual methods aren't. [Meyers]"