自学内容网 自学内容网

C++ vtordisp的应用场景

问题代码

#include <iostream>
using namespace std;

class base
{
public:
base() {}
virtual void show() { cout << "base:: show"<<endl; }
private:
int ma;
};

class derive:virtual public base
{
public:
derive() {}
virtual void show() { cout << "derive:: show"; }
private:
int mb;
};

int main()
{
cout << sizeof(derive) << endl;
}

以上代码类大小常规来说应该是如下,占16个字节大小,但是为何是20呢
预期结构

class derive    size(16):
        +---
 0      | {vbptr}
 4      | mb
        +---
        +--- (virtual base base)
 8      | {vfptr}
12      | ma
        +---

实际结构,占大小20个字节

class derive    size(20):
        +---
 0      | {vbptr}
 4      | mb
        +---
8       | (vtordisp for vbase base)
        +--- (virtual base base)
12      | {vfptr}
16      | ma
        +---

derive::$vbtable@:
 0      | 0
 1      | 12 (derived(derive+0)base)

derive::$vftable@:
        | -12
 0      | &(vtordisp) derive::show

derive::show this adjustor: 12
vbi:       class  offset o.vbptr  o.vbte fVtorDisp
            base      12       0       4 1

在这里插入图片描述
经过验证 必须满足以下两个条件

  1. 派生类重写了虚基类的虚函数。
  2. 派生类定义了构造函数或者析构函数。
    才会产生vtordisp.
    这就牵扯到vtordisp了,介绍如下

1. 基本概念回顾

在 C++ 中,当涉及虚继承时,为了确保在派生类对象中能正确定位虚基类的子对象(包含虚基类的数据成员、虚函数等内容),编译器会在派生类对象的内存布局中安排虚基类表指针等相关结构来记录偏移量信息,以实现准确访问虚基类部分。同时,对于有虚函数的类,会存在虚函数表(VTable)来支持多态调用。

vtordisp 主要用于处理虚继承和虚函数结合场景下,构造函数和析构函数中对虚基类指针调整的一种机制,它本质上是编译器为了正确处理对象的初始化和析构顺序、保证虚基类相关操作的正确性而引入的一个额外的字节(在 32 位系统下通常是 4 字节,64 位系统下通常是 8 字节)来存储偏移量相关信息。

2. 应用场景

虚继承与虚函数并存的类层次结构
  • 场景描述:当类层次结构中既有虚继承又有类自身包含虚函数的情况时,在派生类的构造函数和析构函数中,需要准确地处理与虚基类相关的初始化和清理工作,同时还要考虑虚函数机制带来的多态性影响。

3. 编译器相关考虑

  • 不同的编译器对于 vtordisp 的处理可能会有一些差异,有些编译器可能会根据具体的类层次结构复杂度、是否确实存在需要调整虚基类指针偏移量的情况等来决定是否启用 vtordisp 机制以及如何分配相应的字节来存储相关信息。比如在 Visual C++ 编译器中,对于符合特定条件的虚继承和虚函数结合的场景,会自动插入 vtordisp 相关代码来处理对象布局和操作顺序问题,而在 GCC 等其他编译器中,也有其对应的实现方式和判断标准来确保在类似场景下的代码正确性。

如果不想要vtordisp 可以加 #pragma vtordisp(off) 进行关闭。

vtordisp是Visual C++编译器的一个特性,主要用于解决在类继承中,虚函数的调用与对象布局的问题。当你在类中使用了虚函数,并且该类被继承,且继承类覆盖了基类的虚函数时,可能会出现所谓的"跳跃问题"(slicing problem)。

"跳跃问题"是指当你有一个基类的指针指向派生类对象,并且调用了虚拟函数,预期是派生类的函数被调用,但实际上可能会调用基类的函数。这是因为编译器为了能够快速地调用虚拟函数,直接使用了指针的偏移量来计算虚拟函数的地址,而不是检查实际对象的类型。

为了解决这个问题,当你在类中有一个或多个虚拟函数,并且类被继承,且派生类覆盖了基类的虚函数时,编译器可能会为类添加一个额外的隐藏成员,称为vtordisp字段。vtordisp字段的作用是在构造函数和析构函数执行过程中,记录对象的实际类型信息,以便正确地调用虚拟函数。具体由编译器进行管理。

在网上搜集资料并未搞清楚其底层原理,请知道的大佬不吝赐教。


原文地址:https://blog.csdn.net/doubleintfloat/article/details/145019400

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!