【C++】解决菱形继承而产生的虚基表(偏移量表)

继承是面向对象的核心特性,允许代码复用和类的层次结构。切片描述了派生类对象赋值给基类对象时的情况。重定义意味着子类可以覆盖父类的同名成员。派生类的默认成员函数遵循特定的构造和析构顺序。菱形虚拟继承解决了多继承中的数据冗余和二义性问题,通过虚基类确保只有一份共享的数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

继承概念

  继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用

=

  基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。关键字class时默认的继承方式是private,使用struct时默认的继承方式是public

切片和重定义

  派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。而基类对象不能赋值给派生类对象,因为子类继承父类后,可能会出现父类没有的成员变量,比如下面的 _No

  在继承体系中基类和派生类都有独立的作用域,子类和父类中有同名成员,若不指明类域,局部优先,会先调用子类函数。子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义

派生类的默认成员函数

  这六个默认成员函数主要遵守以下规则,先用初始化列表初始化基类的那一部分,在初始化子类自己这一部分。先析构派生类在析构父类,若先析构父类,而子类可能还调用了父类的内容,就会出现野指针等等问题。记住构造先父后子,析构先子后父就行
  友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
  基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例

菱形虚拟继承

  菱形继承:菱形继承是多继承的一种特殊情况。菱形继承的问题:菱形继承有数据冗余和二义性的问题。在 Assistant 的对象中 Person 成员会有两份,从 Studentteacher 对象中各继承了一份,Assistant 对象若使用了 Person 成员,就会报错,指代不明确,除非指明是 Student 或者 teacher 的类域

菱形继承

class A
{
public:
	int _a;
};
class B :  public A
{
public:
	int _b;
};
class C :  public A
{
public:
	int _c;
};
class D : public B, public C
{
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 1;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

对于以上带代码,先看菱形继承的对象模型:因为 B,C 都有 _a, 如果 d._a 这样直接访问就会报错,不确定你访问的是B对象还是C对象中的_a

菱形虚拟继承

class A
{
public:
	int _a;
};
class B : virtual public A
{
public:
	int _b;
};
class C : virtual public A
{
public:
	int _c;
};
class D : public B, public C
{
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	d._a = 0;
	return 0;
}

对于以上带代码,先看菱形虚拟继承的对象模型:



偏移量表内容



  这里可以分析出D对象中将A放到的了对象组成的最下面,这个A同时属于B和C。那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A

思考一:为什么要建偏移量表,而不直接存偏移量?
  因为 A 类对象可能不止一个成员变量,如果多于一个的话,D 里面 B 和 C对象都要存多个偏移量,肯定没有直接放一个偏移量表地址合适,这样极大的减小了 D 对象的大小

思考二:为什么偏移量表里面不直接存 _a 的地址,而是存一个偏移量?
  因为如果你表里存 _a 地址的话, D 实例化出的d1和d2 中的 _a 地址是不一样的,但它们相对B和C对象的偏移量却是相同的。因此存偏移量的话可以保证所有 D 实例化出的 d1, d2, d3等等对象,都公用同一张表

<think>嗯,用户问的是C++虚拟继承的组成元素,除了偏移量还有什么。首先,我需要回顾一下继承本概念。根据引用[2],继承用于解决菱形继承中的数据冗余问题,确保子类只有一份间接父类的数据。应该和函数类似,存储一些必要的信息来支持继承的实现。 用户已经知道存储偏移量,但想知道还有什么其他内容。我记得在继承中,类子对象的位置是在派生类对象中动态确定的,因此需要某种机制在运行时计算偏移。这可能涉及到中存储不止一种偏移量。例如,可能存在两种偏移量:一种是派生类对象到类子对象的偏移,另一种可能是其他相关结构的偏移,比如函数指针到类的偏移? 另外,可能还需要存储一些类型信息或者额外的标志位来帮助处理多个类的情况。不过,C++标准并没有规定具体的实现细节,这取决于编译器。常见的实现方式,比如在Visual C++中,可能包含多个偏移量,用来处理不同层次的继承关系。例如,类可能在派生类对象的不同位置,因此需要不同的偏移量来计算正确的地址。 还需要考虑指针本身的位置。在继承的类中,通常会在对象布局中添加一个指针,指向对应的中的每个条目可能对应不同的用途,比如第一个条目是当前派生类到类的偏移,第二个条目可能是顶层的偏移,或者其他辅助信息。 不过,有没有可能除了偏移量之外,还包含其他类型的信息?例如,RTTI(运行时类型信息)的指针?但通常RTTI信息是存储在函数中的,而不是中。所以可能主要还是偏移量相关的数据。 总结一下,可能的组成元素包括: 1. 类子对象相对于当前对象的偏移量。 2. 类子对象相对于完整对象的偏移量(在多层继承中可能需要)。 3. 可能有多个偏移量来应对不同的继承层次结构。 需要验证这些猜想是否正确。根据引用[1]中的示例代码,菱形虚拟继承的情况下,D类的对象d1包含B和C两个类,而它们都继承自A。在这种情况下,B和C各自的可能存储了到A的偏移量。当D对象被构造时,B和C的指针可能指向各自的,其中存储了从B或C到A的偏移量。此外,可能还有其他偏移量用于处理不同的情况,比如函数的存在或不同层次的结构。 因此,回答用户的问题时,应该指出除了偏移量之外,可能还包含其他偏移量,具体取决于编译器的实现,但通常主要存储的是偏移量信息,没有其他复杂的数据结构。不同的编译器可能有不同的实现方式,但核心功能是提供正确的偏移以定位类子对象。</think>在C++虚拟继承中,(Virtual Base Table)是编译器用于管理类子对象位置的核心机制。根据典型编译器的实现(如Visual C++和GCC),除了存储**偏移量**外,还可能包含以下元素: 1. **多层级偏移量** 中可能包含**两个偏移量**: - **当前派生类到类子对象的偏移量**:用于在访问类成员时,从当前对象地址计算类子对象的实际地址。 - **类子对象到完整对象的偏移量**:在多层继承中,帮助确定类在最终派生类中的位置[^1][^2]。 例如,菱形继承时,`B`和`C`的分别存储从`B`或`C`到`A`的偏移量,而`D`的可能需要额外的偏移量支持。 2. **指针的间接寻址** 本身通过**指针(vbptr)** 访问,该指针存储在派生类对象中。的条目通过索引访问,例如: - 第一个条目:当前类到类的偏移量 - 第二个条目:类到完整对象的偏移量(若有需要)。 3. **编译器特定的元数据** 某些编译器可能在中添加**标志位或类型信息**,用于调试或动态类型识别(RTTI),但这类信息通常与函数(vtable)结合使用,而非独立存储在中。 ### 示例分析 以菱形继承为例: ```cpp class D : public B, public C { int d; }; ``` - `B`和`C`各自包含指针,指向其。 - `B`的存储从`B`到`A`的偏移量,`C`同理。 - 当通过`D`访问`A::a`时,需通过`B`或`C`的找到`A`的位置[^2]。 ### 总结 的核心内容是**偏移量**,但具体实现可能包含多个偏移量以支持复杂继承关系。不同编译器可能有细微差异,但均围绕动态定位类子对象展开。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰瑞的猫^_^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值