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

    virtual void f() {}
};

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

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

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

4

4 回答 4

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

于 2013-08-27T04:07:40.857 回答
2

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

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

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

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

于 2013-08-27T04:36:47.767 回答
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;
};
于 2013-08-27T04:37:38.970 回答
-3

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

this->member

或者

this->member(...)

视情况而定,因此如果它是虚拟功能,则虚拟发送。该标准对构造函数或析构函数的调用没有任何例外。它确实对调用哪个函数有一个例外,但对如何完成没有例外。

编辑

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

[1] Lippman, Stanley B., *Inside the C++ Object Model,* Addison Wesley 1996, pp179ff.
于 2013-08-27T04:04:43.717 回答