Reputation: 21803
struct A
{
virtual ~A() { f(); }
virtual void f() {}
};
I've edited my question to be more specific..
In this code sample, MAY the call f()
use virtual dispatch, or is it guaranteed equivalent to A::f()
?
Could you provide relevant section from C++ standard? Thanks.
Upvotes: 4
Views: 884
Reputation: 40633
Within a constructor or destructor, the sub-class object has either not yet been constructed, or has already been destroyed. As a result, virtual dispatch does not lead to the derived-class version being used, and instead the base-class version is called.
From the standard, [class.cdtor]/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 the 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.
An example of this is given:
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
}
Also note that this can be unsafe if the function that is called is pure virtual, from [class.abstract]/6
:
Member functions can be called from a constructor (or destructor) of an abstract class; the effect of making a virtual call (10.3) to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined.
Upvotes: 7
Reputation: 310990
Yes. Any unqualified reference to a member from within its class is entirely equivalent to
this->member
or
this->member(...)
as appropriate, and therefore despatched virtually if it's a virtual function. The standard doesn't make any exceptions about calls from constructors or destructors. It does make an exception about which function is called, but not about how that is accomplished.
EDIT
The actual VFT mechanism used to implement this exception is described in [1]. As Lippman points out, simply erasing virtual despath is not an acceptable technique, as any indirect virtual function call called by the called virtual function is also subject to the same exception ('directly or indirectly' clause).
[1] Lippman, Stanley B., *Inside the C++ Object Model,* Addison Wesley 1996, pp179ff.
Upvotes: -3
Reputation: 138201
§12.7.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.
§10.4.6:
Member functions can be called from a constructor (or destructor) of an abstract class; the effect of making a virtual call (10.3) to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined.
Virtual dispatch occurs and the function called is the last override in the class chain until the one being constructed. If the function was never implemented, the behavior is undefined.
// well-defined behavior, calls A::f()
struct A
{
A() { f(); }
virtual void f();
};
struct B
{
virtual void f();
};
// well-defined behavior, calls B::f()
struct C : public B
{
C() { f(); }
};
// well-defined behavior, calls D::f()
struct D : public B
{
D() { f(); }
virtual void f();
};
// calling a pure virtual method from a constructor: undefined behavior
// (even if D::f() is implemented at a later point)
struct D
{
D() { f(); }
virtual void f() = 0;
};
// specifying f() as pure virtual even if it has an implementation in B,
// then calling it from the constructor: undefined behavior
struct E : public B
{
E() { f(); }
virtual void f() = 0;
};
Upvotes: 1
Reputation: 208436
The standard does not require that the call is performed dynamically or statically. Conceptually it is dynamic, and all of the quotes regarding the call of a virtual function while an object is being constructed or destructed contain the text directly or indirectly. That is important, as it entails that whether the call is direct to the function or not the behavior should be the same. Now consider:
struct A {
A() { f(); }
void f() { g(); }
virtual void g() {};
};
And assume that code is not visible and all the usual caveats so that the functions are not inlined. The definition of A::f()
which might be in a different translation unit, does not know whether it is called from the constructor or destructor or neither of them. It does not know if the complete object is of type A
or Z
for any derived type Z
, so it must use dynamic dispatch.
Now, the as-if rule means that the compiler has some leeway to optimize, and it can decide that within the body of the constructor/destructor (or any function inlined into it) the final overrider is known and it can thus avoid dynamic dispatch and call the known final overrider directly. This works even for pure virtual functions, since in that case, the behavior is undefined, and thus there are no guarantees of behavior --so any transformation by the compiler will be valid in that case.
Upvotes: 2