C++ 构建/销毁期间的虚拟调用
C++标准12.7/4规定: 当从构造函数或析构函数直接或间接调用虚函数时,包括在类的非静态数据成员的构造或销毁过程中,调用应用的对象是正在构造或销毁的对象(称之为C++ 构建/销毁期间的虚拟调用,c++,constructor,polymorphism,language-lawyer,virtual-functions,C++,Constructor,Polymorphism,Language Lawyer,Virtual Functions,C++标准12.7/4规定: 当从构造函数或析构函数直接或间接调用虚函数时,包括在类的非静态数据成员的构造或销毁过程中,调用应用的对象是正在构造或销毁的对象(称之为x),调用的函数是构造函数或析构函数类中的最终重写器,而不是在派生类中重写它的函数。如果虚拟函数调用使用显式类成员访问(5.2.5),并且对象表达式引用x的完整对象或该对象的一个基类子对象,但不引用x或其一个基类子对象,则行为未定义 这个文本在我检查的所有版本中都是相同的(尽管在C++03中是第12.7/3段) 我的问题是关于短语“使
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;
}
#包括
结构A{
虚空f(){std::cout它不应该有任何区别
标准上说:
直接或间接调用虚函数时
然而,您的编译器可能有一个bug-可能是因为它优化了h
中的代码,认为它了解正在发生的事情(实际上并没有做正确的事情)。您没有提到您正在使用的编译器,因此无法确定是否有缺陷报告
编辑:几周前发布的g++4.8.2和clang++3.5(如果有区别的话,使用-std=c++11)在析构函数中调用c::f()
,在第一个测试用例的构造函数中调用a::f()
。在第二个测试用例中,g++调用a::f()
,其中clang++调用c::f()
。很明显,编译器在这里似乎“随心所欲”。[请注意,由于它是“未定义的”,它可以做各种不同的事情,包括“您期望的”]
(在第一个测试用例中,我将p
修改为a
,使其能够编译,并在f
和g
函数中添加了打印输出)9.3.1/3(In)表示
当id表达式(5.1)不属于类成员访问语法(5.2.5)的一部分且不用于形成
指向成员(5.3.1)的指针用于X类成员的上下文(5.1.1),
如果名称查找(3.4)将id表达式中的名称解析为某个类的非静态非类型成员
C、 如果id表达式可能被计算,或者C是X或X的基类,则id表达式为
转换为类成员访问表达式(5.2.5),使用(*this)(9.3.2)作为
运算符的左侧
在第二个示例中,这意味着A::h()的主体被转换为(*this).f(),使调用成为显式的类成员访问。因此,12.7/4的最后一行适用;行为未定义。抱歉,修复了第二个程序。我应该知道发布我实际尝试过的内容之一,而不是“基本相同”的内容.所谓缺陷报告,我的意思是与标准本身相反。如果这句话不适用,我不清楚实际应该发生什么。就像引用的部分所说的“直接或间接”-因此,如果您直接或间接调用函数,它应该具有相同的行为。我认为没有必要在这一点上进行更正。因此,您的结论是,即使最后一句不适用,第二个程序也有UB?这确实是我的结论。一般来说,最好避免在构造函数和析构函数中进行虚拟调用s、 因为很容易出错,并最终执行未定义的操作。那么,什么时候会有不使用显式类成员访问的虚拟函数调用?我想不出会发生这种情况,因为在这种情况下没有指定任何内容。短语“使用显式类成员访问”这显然是胡说八道;这是需要纠正的!