C++ C++;构造函数:为什么这个虚拟函数调用不安全?
这来自C++11标准第12.7.4节。这相当令人困惑C++ C++;构造函数:为什么这个虚拟函数调用不安全?,c++,c++11,constructor,virtual-functions,virtual-inheritance,C++,C++11,Constructor,Virtual Functions,Virtual Inheritance,这来自C++11标准第12.7.4节。这相当令人困惑 课文中最后一句话的确切意思是什么 为什么B::B中的最后一个方法调用未定义?它不应该只调用a.a::f 可以调用4个成员函数,包括虚拟函数(10.3) 施工或破坏期间(12.6.2)。当一个虚拟函数 直接或间接地从构造函数或 析构函数,包括在构建或销毁 类的非静态数据成员,以及调用 应用是正在构造或销毁的对象(称为x), 调用的函数是构造函数的or中的最终重写器 析构函数的类,而不是在更派生的类中重写它的类。 如果虚拟函数调用使用显式类成员访
B::B
中的最后一个方法调用未定义?它不应该只调用a.a::f李>
可以调用4个成员函数,包括虚拟函数(10.3)
施工或破坏期间(12.6.2)。当一个虚拟函数
直接或间接地从构造函数或
析构函数,包括在构建或销毁
类的非静态数据成员,以及调用
应用是正在构造或销毁的对象(称为x),
调用的函数是构造函数的or中的最终重写器
析构函数的类,而不是在更派生的类中重写它的类。
如果虚拟函数调用使用显式类成员访问
(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
}
-[结束示例]
我是这样理解的:在构建一个对象的过程中,每个子对象构建它的一部分。在本例中,这意味着
V::V()
初始化V
的成员A
初始化A
的成员,依此类推。由于V
是在A
和B
之前初始化的,因此它们都可以依赖V
的成员进行初始化
在本例中,B
的构造函数接受两个指向自身的指针。它的V
部分已经构建好,因此可以安全地调用V->g()。但是,此时D
的A
部件尚未初始化。因此,调用a->f()
访问未初始化的内存,这是未定义的行为
编辑:
在上面的D
中,A
在B
之前被初始化,因此将无法访问A
的未初始化内存。另一方面,一旦A
完全构建,其虚拟功能将被D
的虚拟功能覆盖(实际上:其vtable在构建期间设置为A
,在构建结束后设置为D
)。因此,对a->f()
的调用将在D
初始化之前调用D::f()
。因此,无论哪种方式-A
都是在B
之前或之后构造的-您将对未初始化的对象调用一个方法
虚拟函数部分已经在这里讨论过了,但为了完整起见:调用f()
使用V::f
,因为A
尚未初始化,就B
而言,这是f
的唯一实现g()
调用B::g
,因为B
覆盖了g
您引用的规范性文本的最后一句如下:
如果虚拟函数调用使用显式类成员访问,并且对象表达式引用x
的完整对象或该对象的一个基类子对象,但不引用x
或其一个基类子对象,则行为未定义
诚然,这相当复杂。这个句子的存在是为了限制在存在多重继承的情况下,在构造过程中可以调用什么函数
该示例包含多重继承:D
派生自A
和B
(我们将忽略V
,因为不需要演示行为未定义的原因)。在构造D
对象期间,将调用a
和B
构造函数来构造D
对象的基类子对象
调用B
构造函数时,x
的完整对象的类型为D
。在该构造函数中,a
是指向x
的a
基类子对象的指针。因此,关于a->f()
,我们可以说:
- 正在构造的对象是
D
对象的B
基类子对象(因为此基类子对象是当前正在构造的对象,所以文本将其称为x
)
- 它使用显式类成员访问(在本例中,通过
->
操作符)
x
的完整对象的类型是D
,因为这是正在构造的最派生的类型
- 对象表达式(
a
)指的是x
的完整对象的基类子对象(它指的是正在构造的D
对象的a
基类子对象)
- 对象表达式引用的基类子对象不是
x
,也不是x
的基类子对象:a
不是B
,a
不是B
的基类
因此,根据我们开始时的规则,调用的行为是未定义的
为什么B::B
中的最后一个方法调用未定义?它不应该只调用a.a::f
您引用的规则指出,在构造期间调用构造函数时,“调用的函数是构造函数类中的最终重写器,而不是在派生类中重写它的函数。”
在这种情况下,构造函数的类是B
。由于B
不是从A
派生的,因此虚拟函数没有最终重写器。因此,尝试进行虚拟调用会显示未定义的行为。标准的这一部分是simp
pointer_to_A->f();
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
a->f(); // as in the example
#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;
}