C++ constructors: why is this virtual function call not safe?
这是从C++ 11标准秒秒7.4。这相当令人困惑。
4 Member functions, including virtual functions (10.3), can be called
during construction or destruction (12.6.2). When a virtual function
is called directly or indirectly from a constructor or from a
destructor, including during the construction or destruction of the
class’s non-static data members, and the object to which the call
applies is the object (call it x) under construction or destruction,
the function called is the final overrider in the constructor’s or
destructor’s class and not one overriding it in a more-derived class.
If the virtual function call uses an explicit class member access
(5.2.5) and the object expression refers to the complete object of x
or one of that object’s base class subobjects but not x or one of its
base class subobjects, the behavior is undefined. [ Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 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
}—end example ]
型
该标准的那部分只是告诉您,当您构建一些"大型"对象
例如,考虑这个继承图(箭头指向从派生类到基类)好的。型
。好的。型
假设我们正在构造一个
但是,如果您以某种方式访问椭圆之外的层次结构,并尝试在那里使用多态性,那么行为将变得未定义。例如,如果您从
获得"外部"子层次结构访问权限的唯一方法是,有人以某种方式将指向
该标准将虚拟继承包含在示例中,以演示此规则的包容性。在上图中,基础子对象
注意,即使
本规范的根源导致了实现多态机制的实际考虑。在实际实现中,将vmt指针作为数据字段引入到层次结构中最基本的多态类的对象布局中。派生类不引入它们自己的vmt指针,它们只是为基类(也可能是更长的vmt)引入的指针提供它们自己的特定值。好的。
请看一下标准中的示例。类
1 | pointer_to_A->f(); |
它实际上被翻译成好的。
1 2 3 | V *v_subobject = (V *) pointer_to_A; // go to V vmt = v_subobject->vmt_ptr; // retrieve the table vmt[index_for_f](); // call through the table |
然而,在标准的示例中,同一个
如果此时你试图打电话好的。
1 | a->f(); // as in the example |
上述算法将找到存储在其
这是相当简单的验证与实际实验。让我们把它自己的版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include <iostream> struct V { virtual void f() { std::cout <<"V" << std::endl; } }; struct A : virtual V { virtual void f() { std::cout <<"A" << std::endl; } }; struct B : virtual V { virtual void f() { std::cout <<"B" << std::endl; } B(V*, A*); }; struct D : A, B { virtual void f() {} D() : B((A*)this, this) { } }; B::B(V* v, A* a) { a->f(); // What `f()` is called here??? } int main() { D d; } |
你想叫
网址:http://ideone.com/ua332好的。
这完全是由于我上面描述的原因(大多数编译器按照我上面描述的方式实现多态性)。这就是语言将此类调用描述为未定义的原因。好的。
有人可能会注意到,在这个特定的示例中,正是虚拟继承导致了这种异常行为。是的,这完全是因为
您引用的规范性文本的最后一句话如下:
If the virtual function call uses an explicit class member access and the object expression refers to the complete object of
x or one of that object’s base class subobjects but notx or one of its base class subobjects, the behavior is undefined.
诚然,这相当复杂。这个句子的存在是为了限制在存在多重继承的情况下,在构造过程中可以调用哪些函数。
示例包含多个继承:
调用
正在构造的对象是
D 对象的B 基类子对象(因为这个基类子对象是当前正在构造的对象,所以文本称为x )。它使用显式类成员访问(在本例中,通过
-> 操作符)x 的完整对象的类型是D ,因为它是正在构造的最派生的类型。对象表达式(
A 是指x 的完整对象的基类子对象(指正在构造的D 对象的A 基类子对象)。对象表达式所指的基类子对象不是
x ,也不是x 的基类子对象:A 不是B ,A 不是B 的基类。
因此,根据我们从一开始就开始的规则,调用的行为是未定义的。
Why is the last method call in
B::B undefined? Shouldn't it just calla.A::f ?
您引用的规则规定,在构造期间调用构造函数时,"调用的函数是构造函数类中的最终重写器,而不是在派生类中重写它。"
在这种情况下,构造函数的类是
以下是我如何理解的:在对象的构造过程中,每个子对象构造它的部分。在示例中,它意味着
在本例中,
编辑:
在上面的
虚拟函数部分已经在这里讨论过了,但是为了完整性:对EDOCX1的调用(27)使用