[ 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; b->virtualFun(); delete b; }
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]"
Generated: 2012-03-02 | © 2012 Alan Ezust and Paul Ezust. |