如果在没有限定的情况下从构造函数/析构函数调用虚拟函数,则是否会发生虚拟调度


struct A
{
    virtual ~A() { f(); }
    virtual void f() {}
};

我已经编辑了我的问题以更具体..

在此代码示例中,调用是否可以f()使用虚拟调度,或者是否保证等同于 A::f()

您能提供C++标准中的相关部分吗?谢谢。

在构造函数或析构函数中,子类对象尚未构造或已被销毁。因此,虚拟调度不会导致使用派生类版本,而是调用基类版本。

从标准来看,[class.cdtor]/4

成员函数,包括虚函数 (10.3(,可以在构造或销毁 (12.6.2( 期间调用。当从构造函数或析构函数直接或间接调用虚函数时,包括在构造或销毁类的非静态数据成员期间,并且调用适用的对象是正在构造或销毁的对象(称为 x(,则调用的函数是构造函数或析构函数类中的最终重写器,而不是在派生更多类中重写它的函数。如果虚拟函数调用使用显式类成员访问 (5.2.5(,并且对象表达式引用 x 的完整对象或该对象的基类子对象之一,但不是 x 或其基类子对象之一,则行为是未定义的。

举了一个例子:

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
}

另请注意,如果调用的函数是纯虚拟函数,则这可能是不安全的,从 [class.abstract]/6

成员函数可以从抽象类的构造函数(或析构函数(调用

;对于从此类构造函数(或析构函数(创建(或销毁(的对象,直接或间接对纯虚函数进行虚拟调用(10.3(的效果是不确定的。

该标准不要求动态或静态执行调用。从概念上讲,它是动态的,并且在构造或销毁对象时有关调用虚函数的所有引号都直接或间接包含文本。这很重要,因为它意味着无论调用是否直接针对函数,行为都应该相同。现在考虑:

struct A {
   A() { f(); }
   void f() { g(); }
   virtual void g() {};
};

并假设代码不可见以及所有常见的警告,以便函数不内联。A::f()的定义可能在不同的翻译单元中,不知道它是从构造函数还是析构函数调用的,或者两者都不是。它不知道完整的对象是A类型还是Z派生类型Z,所以它必须使用动态调度。

现在,as-if 规则意味着编译器有一些优化的余地,它可以决定在构造函数/析构函数(或内联到其中的任何函数(的主体中,最终覆盖器是已知的,因此它可以避免动态调度并直接调用已知的最终覆盖器。这甚至适用于纯虚函数,因为在这种情况下,行为是未定义的,因此无法保证行为 - 因此编译器的任何转换在这种情况下都是有效的

§12.7.4:

成员函数,包括虚函数 (10.3(,可以在构造或销毁 (12.6.2( 期间调用。 当从构造函数或析构函数直接或间接调用虚函数时,包括在构造或销毁类的非静态数据成员期间,并且调用适用的对象是正在构造或销毁的对象(称为 x(,则调用的函数是构造函数或析构函数类中的最终重写器,而不是在派生更多类中重写它。

§10.4.6:

成员函数可以从抽象类的构造函数(或析构函数(调用

;对于从此类构造函数(或析构函数(创建(或销毁(的对象,直接或间接对纯虚函数进行虚拟调用(10.3(的效果是不确定的。

发生虚拟调度,调用的函数是类链中的最后一个覆盖,直到被构造的覆盖。如果从未实现该函数,则行为未定义。

// 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;
};

是的。从其类中对成员的任何非限定引用完全等效于

this->member

this->member(...)

如果合适,因此如果是虚拟功能,则以虚拟方式发送。该标准不会对来自构造函数或析构函数的调用做出任何例外。它确实对调用哪个函数进行了例外,但对如何完成没有例外。

编辑

用于实现此异常的实际 VFT 机制在 [1] 中描述。正如 Lippman指出的那样,简单地擦除虚拟 despath 不是一种可接受的技术,因为被调用的虚函数调用的任何间接虚函数调用也受到相同的例外("直接或间接"条款(的约束。

[1] Lippman, Stanley B., *Inside the C++ Object Model,* Addison Wesley 1996, pp179ff.

最新更新