Class

虚函数可以继承 可以在子类中被重写

动态绑定

虚函数被调用时会根据实例类型动态决定被调用函数。

虚函数表可以实现动态绑定 每个实例都持有一个vtable,可以根据vtable确定被调用函数。

无继承

虚函数表的指针会放在对象内存开头的一个字长的位置

单继承

  • 虚函数表内部按照继承顺序存放虚函数表项
  • 子类新定义的虚函数存放到最后

第 1 步:复制 (Copy)

  • 动作:完整地复制一份父类 Parent 的虚函数表作为蓝本。
  • 结果:我们得到一个临时的表,内容和 Parent 的 vtable 一模一样。
    • 索引 0: &Parent::func1
    • 索引 1: &Parent::func2

第 2 步:修改 (Modify / Override)

  • 动作:检查 Child 类是否重写了父类的虚函数。

  • 结果

    • 编译器发现 Child 重写了 func1。于是,它将表中索引为 0 的那一项,从 &Parent::func1 修改为 &Child::func1
    • 编译器发现 Child 没有动 func2,所以索引为 1 的那一项保持不变,仍然是 &Parent::func2

    此时,表的更新为:

    • 索引 0: &Child::func1 (已修改)
    • 索引 1: &Parent::func2 (保持不变)

第 3 步:追加 (Append)

  • 动作:检查 Child 类是否定义了任何新的虚函数(父类中没有的)。
  • 结果
    • 编译器发现了新的虚函数 func3。于是,它将这个新函数的地址 &Child::func3 追加到表的末尾

多继承

一个子类继承了多个父类。

处理方式也很简单 按照声明的顺序以此复制

然后修改、追加。

RTTI机制

Run Time Type Information

运行时获取对象的类型结构信息 继承关系

多态

用一种类型的指针 灵活的取多种class

比如

Animal* cage[] = { new Dog(), new Cat(), new Dog() };

for (Animal* p_animal : cage) {
// 问题来了:p_animal 的静态类型是 Animal*
// 它没有 bark() 方法。
// 我们如何才能只在 p_animal 确实指向 Dog 对象时,
// 才安全地调用 bark() 呢?

// p_animal->bark(); // 编译错误!Animal没有bark()方法
}

实现

由于多态的存在 一个父类指针可能指向各种不同的对象,父类需要随时知道指向的是哪个子类的对象

RTTI解决的核心问题:安全地识别和使用对象的真实类型,而不是它在编译时表现出的类型。

两大核心工具

dynamic_cast

作用: 安全地将父类指针转换为子类指针

原理是运行时检查RTTI信息,如果是想要的类型 才会返回有效的指针。

用法

Dog* p_dog = dynamic_cast<Dog*>(p_animal);

** typeid **

返回type_info类地常量对象引用

用法

Dog my_dog;
Animal* p_animal = &my_dog;

// 获取对象的精确类型信息
const std::type_info& info1 = typeid(my_dog);
const std::type_info& info2 = typeid(*p_animal); // 注意要解引用

if (info1 == info2) {
// 他们的类型完全相同
}

// .name() 返回一个编译器实现相关的、可能被“混淆”过的类型名
std::cout << info1.name() << std::endl; // 可能会输出像 "3Dog" 这样的名字

typeid更多用于比较

识别对象的原理

GCC中type_info其实也有不同的派生类

根据复杂度有如下几种

类类型 对应的派生类 包含的信息
基础类型 (int, float) __fundamental_type_info 仅类型名
无基类的类 __class_type_info 仅类型名
单一公有继承 __si_class_type_info 指向基类 type_info 的指针
多重/虚继承/非公有 __vmi_class_type_info 标志位、基类列表、偏移量属性
Object Pointer --> [ vptr ] --+
|
+-----------------------+
v
[ vtable ]
+-------------------+
| offset-to-top | (Index -2) : 对象起始地址到当前的偏移
+-------------------+
| type_info 指针 | (Index -1) : 指向该类的 __xxx_type_info 对象
+-------------------+
| virtual_func_1() | (Index 0) : 第一个虚函数
+-------------------+
| virtual_func_2() |
+-------------------+

** 核心 **: __vmi_class_type_info

__base_class_type_info 结构

每个基类的信息都封装在这个结构中,它包含两个关键部分:

  1. __base_type: 指向基类 type_info 的指针。
  2. __offset_flags: 这是一个 long 类型,通过位运算存储了位移和属性。

定位起始点:通过 src 对象的 vptr 找到 vtable[-1],获取当前对象的 __vmi_class_type_info

搜索家谱

  • 如果当前类就是 Dst,根据 offset-to-top 计算地址并返回。
  • 如果是继承关系,它会递归遍历 __vmi_class_type_info 里的 base_info 数组。

计算偏移

  • 它会检查每个基类的 __offset_flags
  • 如果是普通继承,直接加上 offset
  • 如果是虚继承__virtual_mask 为 1),它会去对象的 vbtable (或在 vtable 里的虚基类偏移区域) 查找动态偏移。

权限校验:检查 __public_mask。如果路径上有 private 继承且你不是在类内部转换,转换会失败返回 nullptr

stack

现代编译器更青睐于使用rsp