如果从构造函数/析构函数中调用虚函数而没有限定,是否会发生虚拟调度?

时间:2013-08-27 03:59:24

标签: c++

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

    virtual void f() {}
};

我已将我的问题编辑为更具体......

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

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

4 个答案:

答案 0 :(得分:7)

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

从标准[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)的效​​果是未定义的。

答案 1 :(得分:2)

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

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

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

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

答案 2 :(得分:0)

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

答案 3 :(得分:-3)

是。对其成员的任何不合格的引用完全等同于

this->member

this->member(...)

视情况而定,因此如果它是虚函数则虚拟发送。关于来自构造函数或析构函数的调用,该标准不做任何例外。它确实调用了哪个函数,但不是关于如何实现它。

修改

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

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