施工/销毁期间的虚拟电话

时间:2014-06-30 23:27:09

标签: c++ constructor polymorphism language-lawyer virtual-functions

C ++标准版12.7 / 4说:

  

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

我检查的所有版本中的文本相同(尽管在C ++ 03中它是第12.7 / 3段)。

我的问题是关于短语" 使用明确的类成员访问"。可能该短语的要点是指出在构造函数/析构函数体中,使用隐式this->的虚拟调用是安全的,因为对象表达式确实引用了对象x

struct A;
A* p;

struct A {
    A() { p = this; }
    virtual ~A() { if (p == this) p = nullptr; }
    virtual void f() {}
};

struct B {
    B();
    virtual ~B();
    virtual void g() {}
};

struct C : public A, public B {
    virtual void f() {}
    virtual void g() {}
};

B::B() {
    if (p) p->f(); // UB if `p` and `this` point at same complete object
    g();           // Definitely safe, calls B::g().
}

B::~B() {
    if (p) p->f(); // UB if `p` and `this` point at same complete object
    g();           // Definitely safe, calls B::g().
}

int main() {
    C c;      // UB in B::B() and B::~B()!
}

但是如果虚函数调用在构造函数或析构函数的定义中不是语法上的,而是间接调用的呢?这个程序的行为是什么?

#include <iostream>

struct A {
    virtual void f() { std::cout << "A::f()\n"; }
    void h() { f(); }
};

struct B {
    explicit B(A& a) { a.h(); }
};

struct C : public A, public B {
    C() : A(), B(static_cast<A&>(*this)) {}
    virtual void f() { std::cout << "C::f()\n"; }
};

int main() {
    C c;
}

我希望在B::B(A&)中,调用a.h()与调用a.f()一样未定义。但我们不能说12.7 / 4中的最后一个句子适用,因为虚函数调用不使用显式类成员访问。我错过了什么吗?在这种情况下,a.f()a.h()真的应该采取不同的行动吗?是否有与此相关的缺陷报告?应该有吗?

2 个答案:

答案 0 :(得分:2)

它应该没有任何区别。

标准说:

  

直接或间接调用虚函数时

然而,你的编译器可能有一个错误 - 也许是因为它优化了h中的代码,认为它理解发生的事情(并没有真正做正确的事情)。您还没有提到您正在使用的WHICH编译器,因此无法确定是否存在缺陷报告...

编辑:几周前发布之前的g ++ 4.8.2和clang ++ 3.5(使用-std = c ++ 11,如果有所不同)在析构函数中调用C::f(),{ {1}}在您的第一个测试用例的构造函数中。在第二个测试用例中,g ++调用A::f(),其中clang ++调用A::f()。很明显,编译器似乎做了什么&#34;无论感觉如何&#34;这里。 [注意,因为它&#34;未定义&#34;,它可以做各种不同的事情,包括&#34;你期望的&#34;]。

(在第一个测试案例中,我将C::f()修改为p以使其编译,并在af函数中添加了打印输出

答案 1 :(得分:2)

9.3.1 / 3(在N3485中)说

  

当一个id-expression(5.1)不属于类成员访问语法(5.2.5)而不习惯形成   成员(5.3.1)的指针在可以使用它的上下文中的类X的成员中使用(5.1.1),   如果name lookup(3.4)将id-expression中的名称解析为某个类的非静态非类型成员   C,如果id-expression可能被评估,或者C是X或X的基类,则id-expression为   使用(* this)(9.3.2)作为post fi x-expression转换为类成员访问表达式(5.2.5)   左边的。操作

在你的第二个例子中,这意味着A :: h()的主体被转换为(* this).f(),使得调用成为显式的类成员访问。因此,最后一行12.7 / 4适用;行为未定义。