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()
真的应该采取不同的行动吗?是否有与此相关的缺陷报告?应该有吗?
答案 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
以使其编译,并在a
和f
函数中添加了打印输出
答案 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适用;行为未定义。