This is from the C++11 standard sec 12.7.4. This is rather confusing.
- What does the last sentence in the text mean exactly?
- Why is the last method call in
B::B
undefined? Shoudn't it just calla.A::f
?
4 Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class’s non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class. If the virtual function call uses an explicit class member access (5.2.5) and the object expression refers to the complete object of x or one of that object’s base class subobjects but not x or one of its base class subobjects, the behavior is undefined. [ Example:
struct V { virtual void f(); virtual void g(); }; struct A : virtual V { virtual void f(); }; struct B : virtual V { virtual void g(); B(V*, A*); }; struct D : A, B { virtual void f(); virtual void g(); D() : B((A*)this, this) { } }; B::B(V* v, A* a) { f(); // calls V::f, not A::f g(); // calls B::g, not D::g v->g(); // v is base of B, the call is well-defined, calls B::g a->f(); // undefined behavior, a’s type not a base of B }
—end example ]
That portion of the standard is simply telling you that when you are constructing some "large" object
J
whose base class hierarchy includes multiple inheritance, and you are currently sitting inside the constructor of some base subobjectH
, then you are only allowed to use polymorphism ofH
and its direct and indirect base subobjects. You are not allowed to use any polymorphism outside that subhierarchy.For example, consider this inheritance diagram (arrows point from derived classes to base classes)
Let's say we are constructing a "large" object of type
J
. And we are currently executing the constructor of classH
. Inside the constructor ofH
you are allowed to enjoy typical constructor-restricted polymorphism of the subhierarchy inside the red oval. For example, you can call virtual functions of base subobject of typeB
, and the polymorphic behavior will work as expected inside the circled subhierarchy ("as expected" means that the polymorphic behavior will go as low asH
in the hierarchy, but no lower). You can also call virtual functions ofA
,E
,X
and other subobjects that fall inside the red oval.However, if you somehow gain access to the hierarchy outside the oval and attempt to use polymorphism there, the behavior becomes undefined. For example, if you somehow gain access to
G
subobject from the constructor ofH
and attempt to call a virtual function ofG
- the behavior is undefined. The same can be said about calling virtual functions ofD
andI
from the constructor ofH
.The only way to obtain such access to the "outside" subhierarchy is if someone somehow passed a pointer/reference to
G
subobject into the constructor ofH
. Hence the reference to "explicit class member access" in the standard text (although it seems to be excessive).The standard includes virtual inheritance into the example to demonstrate how inclusive this rule is. In the above diagram base subobject
X
is shared by both the subhierarchy inside the oval and subhierarchy outside the oval. The standard says that it is OK to call virtual functions ofX
subobject from the constructor ofH
.Note that this restriction applies even if the construction of
D
,G
andI
subobjects has been finished before the construction ofH
began.The roots of this specification lead to practical consideration of implementing polymorphic mechanism. In practical implementations the VMT pointer is introduced as a data field into the object layout of the most basic polymorphic classes in the hierarchy. Derived classes don't introduce their own VMT pointers, they simply provide their own specific values for the pointers introduced by the base classes (and, possibly, longer VMTs).
Take a look at the example from the standard. The class
A
is derived from classV
. This means that the VMT pointer ofA
physically belongs toV
subobject. All calls to virtual functions introduced byV
are dispatched through VMT pointer introduced byV
. I.e. whenever you callit is actually translated into
However, in the example from the standard the very same
V
subobject is also embedded intoB
. In order to make the constructor-restricted polymorphism work correctly, the compiler will place a pointer toB
's VMT into VMT pointer stored inV
(because whileB
's constructor is activeV
subobject has to act as part ofB
).If at this moment you somehow attempt to call
the above algorithm will find
B
's VMT pointer stored in itsV
subobject and will attempt to callf()
through that VMT. This obviously makes no sense at all. I.e. having virtual methods ofA
dispatched throughB
's VMT makes no sense. The behavior is undefined.This is rather simple to verify with practical experiment. Let's add its own version of
f
toB
and do thisYou expect
A::f
to be called here? I tried several compilers, an all of them actually callB::f
! Meanwhile, thethis
pointer valueB::f
receives in such call is completely bogus.http://ideone.com/Ua332
This happens exactly for the reasons I described above (most compilers implement polymorphism the way I described above). This is the reason the language describes such calls as undefined.
One might note that in this specific example it is actually the virtual inheritance that leads to this unusual behavior. Yes, it happens exactly because the
V
subobject is shared betweenA
andB
subobjects. It is quite possible that without virtual inheritance the behavior would be much more predictable. However, the language specification apparently decided to just draw line the the way it is drawn in my diagram: when you are constructingH
you are not allowed to step out of the "sandbox" ofH
's subhierarchy regardless of what inheritance type is used.The last sentence of the normative text that you cite reads as follows:
This is, admittedly, rather convoluted. This sentence exists to restrict what functions may be called during construction in the presence of multiple inheritance.
The example contains multiple inheritance:
D
derives fromA
andB
(we'll ignoreV
, because it is not required to demonstrate why the behavior is undefined). During construction of aD
object, both theA
andB
constructors will be called to construct the base class subobjects of theD
object.When the
B
constructor is called, the type of the complete object ofx
isD
. In that constructor,a
is a pointer to theA
base class subobject ofx
. So, we can say the following abouta->f()
:The object under construction is the
B
base class subobject of aD
object (because this base class subobject is the object currently under construction, it is what the text refers to asx
).It uses explicit class member access (via the
->
operator, in this case)The type of the complete object of
x
isD
, because that is the most-derived type that is being constructedThe object expression (
a
) refers to a base class subobject of the complete object ofx
(it refers to theA
base class subobject of theD
object being constructed)The base class subobject to which the object expression refers is not
x
and is not a base class subobject ofx
:A
is notB
andA
is not a base class ofB
.Therefore, the behavior of the call is undefined, per the rule we started from at the beginning.
The rule you cite states that when a constructor is called during construction, "the function called is the final overrider in the constructor’s class and not one overriding it in a more-derived class."
In this case, the constructor's class is
B
. BecauseB
does not derive fromA
, there is no final overrider for the virtual function. Therefore the attempt to make the virtual call exhibits undefined behavior.Here's how I understand this: During the construction of an object, each sub-object constructs its part. In the example, it means that
V::V()
initializesV
's members;A
initializesA
's members, and so on. SinceV
is initialized beforeA
andB
, they can both rely onV
's members to be initialized.In the example,
B
's constructor accepts two pointers to itself. ItsV
part is already constructed, so it's safe to callv->g()
. However, at that pointD
'sA
part has not been initialized yet. Therefore, the calla->f()
accesses uninitialized memory, which is undefined behavior.Edit:
In the
D
above,A
is initialized beforeB
, so there won't be any access toA
's uninitialized memory. On the other hand, onceA
has been fully constructed, its virtual functions are overridden by those ofD
(in practice: its vtable is set toA
's during construction, and toD
's once the construction is over). Therefore, the call toa->f()
will invokeD::f()
, beforeD
has been initialized. So either way -A
is constructed beforeB
or after - you're going to call a method on an uninitialized object.The virtual functions part has already been discussed here, but for completeness: the call to
f()
usesV::f
becauseA
has not been initialized yet, and as far asB
is concerned, that's the only implementation off
.g()
callsB::g
becauseB
overridesg
.