When
an object containing virtual functions is created, its VPTR must
be initialized to point to the proper VTABLE.
This must be done before there’s any possibility of calling a virtual
function. As you might guess, because the constructor has the job of bringing
an object into existence, it is also the constructor’s job to set up the
VPTR. The compiler secretly inserts code into the beginning of the constructor
that initializes the VPTR. In fact, even if you don’t explicitly create a
constructor for a class, the compiler will create one for you with the proper
VPTR initialization code (if you have virtual functions). This has several
implications.
The
first concerns efficiency. The reason for
inline
functions is to reduce the calling overhead for small functions. If C++
didn’t provide
inline
functions, the preprocessor might be used to create these “macros.”
However, the preprocessor has no concept of access or classes, and therefore
couldn’t be used to create member function macros. In addition, with
constructors that must have hidden code inserted by the compiler, a
preprocessor macro wouldn’t work at all.
You
must be aware when hunting for efficiency
holes that the compiler is inserting hidden code into your constructor
function. Not only must it initialize the VPTR, it must also check the value of
this
(in case the
operatornew
returns zero) and call base-class constructors. Taken together, this code can
impact what you thought was a tiny inline function call. In particular, the
size of the constructor can overwhelm the savings you get from reduced
function-call overhead. If you make a lot of inline constructor calls, your
code size can grow without any benefits in speed.
Of
course, you probably won’t make all tiny constructors non-inline right
away, because they’re much easier to write as inlines. But when
you’re tuning your code, remember to remove inline constructors.
Order
of constructor calls
The
second interesting facet of constructors and virtual functions concerns the
order of constructor calls and the way virtual calls are made within
constructors.
All
base-class constructors are always called in the constructor for an inherited
class. This makes sense because the constructor has a special job: to see that
the object is built properly. A derived class has access only to its own
members, and not those of the base class; only the base-class constructor can
properly initialize its own elements. Therefore it’s essential that all
constructors get called; otherwise the entire object wouldn’t be
constructed properly. That’s why the compiler enforces a constructor call
for every portion of a derived class. It will call the default constructor if
you don’t explicitly call a base-class constructor in the constructor
initializer list. If there is no default constructor, the compiler will
complain. (In this example,
class
X
has no constructors so the compiler can automatically make a default
constructor.)
The
order of the constructor calls is important. When you inherit, you know all
about the base class and can access any
public
and
protected
members of the base class. This means you must be able to assume that all the
members of the base class are valid when you’re in the derived class. In
a normal member function, construction has already taken place, so all the
members of all parts of the object have been built.
Inside
the constructor, however, you must be able to assume that all members that you
use have been built. The only way to guarantee this is for the base-class
constructor to be called first. Then when you’re in the derived-class
constructor, all the members you can access in the base class have been
initialized. “Knowing all members are valid” inside the constructor
is also the reason that, whenever possible, you should initialize all member
objects (that is, objects placed in the class using composition) in the
constructor initializer list. If you follow this practice, you can assume that
all base class members
and
member objects of the current object have been initialized.
Behavior
of virtual functions inside constructors
The
hierarchy of constructor calls brings up an interesting dilemma. What happens
if you’re inside a constructor and you call a virtual function? Inside an
ordinary member function you can imagine what will happen – the virtual
call is resolved at run-time because the object cannot know whether it belongs
to the class the member function is in, or some class derived from it. For
consistency, you might think this is what should happen inside constructors.
This
is not the case. If you call a virtual function inside a constructor, only the
local version of the function is used. That is, the virtual mechanism
doesn’t work within the constructor.
This
behavior makes sense for two reasons. Conceptually, the constructor’s job
is to bring the object into existence (which is hardly an ordinary feat).
Inside any constructor, the object may only be partially formed – you can
only know that the base-class objects have been initialized, but you cannot
know which classes are inherited from you. A virtual function call, however,
reaches “forward” or “outward” into the inheritance
hierarchy. It calls a function in a derived class. If you could do this inside
a constructor, you’d be calling a function that might manipulate members
that hadn’t been initialized yet, a sure recipe for disaster.
The
second reason is a mechanical one. When a constructor is called, one of the
first things it does is initialize its VPTR.
However, it can only know that it is of the “current” type. The
constructor code is completely ignorant of whether or not the object is in the
base of another class. When the compiler generates code for that constructor,
it generates code for a constructor of that class, not a base class and not a
class derived from it (because a class can’t know who inherits it). So
the VPTR it uses must be for the VTABLE
of that class. The VPTR remains initialized to that VTABLE for the rest of the
object’s lifetime
unless
this isn’t the last constructor call. If a more-derived constructor is
called afterwards, that constructor sets the VPTR to
its
VTABLE, and so on, until the last constructor finishes. The state of the VPTR
is determined by the constructor that is called last. This is another reason
why the constructors are called in order from base to most-derived.
But
while all this series of constructor calls is taking place, each constructor
has set the VPTR to its own VTABLE. If it uses the virtual mechanism for
function calls, it will produce only a call through its own VTABLE, not the
most-derived VTABLE (as would be the case after
all
the constructors were called). In addition, many compilers recognize that a
virtual function call is being made inside a constructor, and perform early
binding because they know that late-binding will produce a call only to the
local function. In either event, you won’t get the results you might
expect from a virtual function call inside a constructor.