何时为派生类初始化 vptr?

When is the vptr initialized for derived classes?

本文关键字:vptr 初始化 派生 何时      更新时间:2023-10-16

最近,我一直在阅读"在c ++对象模型内部"。它说 vptr 在调用基类的构造函数后初始化。所以我运行了一个测试:

class A {
public:
A(int i) {
std::cout << i << std::endl;
}
virtual int vfunc() {
return 1;
}
};
class B : public A {
public:
B() : A(vfunc()) {
}
virtual int vfunc() {
return 2;
}
};
int main() {
B b;
}

结果如下:

2

我的问题是,在调用基类 A 的构造函数之前,类 B 是否先设置其 vptr?

你的问题的答案是否定的。如果在执行父构造函数之前设置vptr则意味着该构造函数将覆盖它。

至于你在代码中看到的行为:对构造函数内正在构造的对象的虚函数的任何调用都是在没有vptr的情况下解析的。所以你的代码实际上等效于:

B() : A(B::vfunc()) {
}

没有虚拟呼叫。相关标准措辞([class.cdtor]p3(:

成员函数,包括虚函数 (13.3(,可以调用 在施工或破坏期间(15.6.2(。当虚拟函数 直接或间接从构造函数或从 析构函数,包括在建造或销毁期间 类的非静态数据成员,以及调用的对象 应用是正在建造或破坏的对象(称为X(, 调用的函数是构造函数 or 中的最终覆盖器 析构函数的类,而不是在派生更多的类中重写它的类。

C++ 标准没有说明vptr或设置它。

但是,标准确实说virtual调用取决于调用时对象的类型。在A的构造函数中,类型是A,在B的构造函数中,类型是B。但那是在构造函数的主体内部。由于初始值设定项列表还包括基类的初始化,因此在B::B()的初始值设定项列表开始执行时,对象尚没有任何类型。

正式:

§ 12.6.2.13: 成员函数(包括虚拟成员功能, 10.3(可以调用正在构建的对象。[...]但是,如果这些操作是在 ctor-initializer(或函数中(中执行的 直接或间接从 ctor-初始值设定项调用(,在所有 基类的 mem 初始值设定项已完成,结果为 操作未定义

(强调我的(

在类构建/销毁期间,任何虚拟调用都将被解析,就好像类的覆盖是最终覆盖一样。我几乎 100% 确定这种行为是标准规定的,但你最好检查一下。例:

http://cpp.sh/2gyz5

// Example program
#include <iostream>
#include <string>
class A{
public:
virtual void f1(){
std::cout<<"1A"<<std::endl;
}
virtual void f2(){
std::cout<<"2A"<<std::endl;
}
A(){
f1();
}
virtual ~A(){
f1();
}
};
class B : public A{
public:
virtual void f1(){
std::cout<<"1B"<<std::endl;
}
virtual void f2(){
std::cout<<"2B"<<std::endl;
}
B():A(){
f2();
}
~B(){
f2();
}
};
class C: public B{
public:
virtual void f1(){
std::cout<<"1C"<<std::endl;
}
virtual void f2(){
std::cout<<"2C"<<std::endl;
}
C():B(){
f2();
}
~C(){}
};
int main(){
C c;
return 0;
}

它将产生输出

1A
2B
2C
2B
1A

因为调用顺序将是 C::

C B::B A::A A::f1 B::f2 C::~C B::~B B::~F A::~F A::~A A::f1