C++虚函数、虚继承、虚基类

虚函数 & 虚继承 & 虚基类

概念 virtual 写在何处? 核心作用 内存/底层机制
虚函数 成员函数声明前 virtual void foo(); 允许子类重写,实现动态多态(运行时确定调用哪个函数)。 对象中生成 vptr 指向 vtable(虚函数表)。
虚继承 继承列表的类名前 class B : virtual public A 解决菱形继承导致的数据冗余与二义性 对象中生成 vbptr 指向 vbtable(虚基类表),记录偏移量。
虚基类 virtual 继承的那个基类 在上例中 A 就是虚基类 作为多重继承中被共享的基类子对象。 在最顶层的派生类(如 D)内存布局中,只保留一份实例。
  • "虚基类"是一个相对的概念A 本身定义时就是一个普通的 class A。只有当 Bvirtual public A 继承它时,在 B(以及后代 D)的视角里,A 才被称为 AB 的虚基类
  • 两者可以叠加 :一个类可以既有虚函数,又被虚继承。此时对象内部会同时存在 vptr(处理虚函数)和 vbptr(处理虚继承偏移)。

虚继承与虚基类

  • 应用场景: 解决多继承中的菱形继承问题。
  • 核心痛点: 若类 B 和类 C 都继承自类 A,而类 D 同时继承 B 和 C,则类 D 中会包含两份类 A 的数据成员,导致数据冗余与访问二义性。
  • 实现原理:
    • 使用 class B : virtual public A 进行虚继承。
    • 编译器会在派生类中加入 虚基类指针(vbptr) ,指向 虚基类表(vbtable),通过偏移量找到唯一的基类 A 成员,确保派生类中只有一份共享的基类子对象。

虚函数与多态

  • 动态多态: 依靠 virtual 关键字实现"运行时绑定"。
  • 底层机制:
  • 虚函数表指针(vptr): 每一个包含虚函数的类对象内部都隐式持有一个 vptr 指针,指向该类的 vtable
  • 虚函数表(vtable): 存储类中所有虚函数的入口地址。在运行时,通过指针/引用调用虚函数时,从 vtable 中查找对应的实际执行函数。
  • 虚析构函数: 当通过基类指针销毁派生类对象时,基类的析构函数必须声明为 virtual,否则只会调用基类析构函数,导致派生类特有资源内存泄漏。

Virtual

  • 作用:
    • 加在函数前面virtual void foo(); →\rightarrow→ 虚函数(实现动态多态)。
    • 加在继承方式前面class B : virtual public A →\rightarrow→ 虚继承A 称为 虚基类)。
  • 接口
    • 普通虚函数(有函数体) :定义默认行为,子类可以重写,也可以直接复用。
    • 纯虚函数(= 0 :专门用来定义规范/接口,强制要求派生类必须实现。

虚函数(加在成员函数前)

cpp 复制代码
#include <iostream>

class Base {
public:
    virtual void show() { // 这里的 virtual 表示"虚函数"
        std::cout << "Base show" << std::endl;
    }
};

class Derived : public Base { // 普通单继承
public:
    void show() override {
        std::cout << "Derived show" << std::endl;
    }
};

虚继承与虚基类(加在继承列表前)

  • 定义:只有在类继承时 使用 virtual 关键字,才叫虚继承 ;被虚继承的那个基类,才叫虚基类
  • 虚继承的作用:解决菱形继承问题
  • virtual的作用:保证的是在这一个 D 对象 的内存空间里,基类 A 的成员只保留一份,而不是多个子对象各复制一份
cpp 复制代码
#include <iostream>

// 1. 祖先类(待会被虚继承,它就是"虚基类")
class A {
public:
    int m_data = 10;
};

// 2. 类 B 虚继承类 A(A 是 B 的虚基类)
class B : virtual public A { 
    // 注意看这里:virtual 加在 public A 前面!
};

// 3. 类 C 同样虚继承类 A(A 也是 C 的虚基类)
class C : virtual public A {
    // 同样是 virtual public A
};

// 4. 类 D 多继承 B 和 C(菱形继承的终点)
class D : public B, public C {
    // D 继承了 B 和 C
};

int main() {
    D d;
    // 因为用了虚继承,d 内部只有【一份】唯一的 A 对象!
    d.m_data = 100; // ✅ 直接访问,没有歧义!

    // 如果没有加 virtual 虚继承:
    // d.m_data 会报错:error: request for member 'm_data' is ambiguous
    // 因为必须写成 d.B::m_data 或 d.C::m_data
}

纯虚函数 & 抽象类

说明

  • 英文:Pure Virtual Function
  • 纯虚函数:只要在虚函数后面加上 = 0,它就变成了纯虚函数
  • 抽象类:包含至少一个纯虚函数的类,就叫做抽象类(Abstract Class)
cpp 复制代码
// 1. 抽象基类(相当于 Java 的 interface / abstract class)
class Shape {
public:
    virtual void draw() = 0;      // 纯虚函数 1
    virtual void rotate() = 0;    // 纯虚函数 2
    virtual ~Shape() {}           // 记得虚析构!
};

// Shape s; // ❌ 编译报错!抽象类不能直接实例化对象

特点

  • 如果子类继承了抽象类,但没有全部重写 所有的纯虚函数,那么这个子类依然是一个抽象类,同样不能被实例化。
cpp 复制代码
// 2. 子类 1:只实现了 draw(),没有实现 rotate()
class Polygon : public Shape {
public:
    void draw() override {
        // 实现了 draw
    }
    // rotate() 依然是纯虚函数!
};

// Polygon p; // ❌ 编译报错!Polygon 依然是抽象类,不能实例化!

// 3. 孙子类:终于把剩下的 rotate() 也实现了
class Triangle : public Polygon {
public:
    void rotate() override {
        // 实现了 rotate
    }
};

Triangle t; // ✅ 成功!所有纯虚函数都被实现了,Triangle 是具体类

虚函数表

  • 英文:Virtual Method Table / vtable
  • 本质: 编译器在编译期为每个包含虚函数的类生成的只读函数指针数组。
  • 内容: 里面按顺序存着该类所有虚函数的入口地址。如果派生类重写了基类的某个虚函数,vtable 对应位置的地址就会被替换为派生类自己的函数地址;如果没重写,就保留基类的函数地址。

虚函数表指针 (vptr)

  • 本质: 存在于对象实例内存开头的一个隐藏指针。
  • 作用: 实例化对象时,vptr 会被初始化指向该类对应的 vtable。当你调用 ptr->foo() 时,CPU 实际执行的是:找到 ptr →\rightarrow→ 读出 vptr →\rightarrow→ 查 vtable 取第 N 个函数指针 →\rightarrow→ 跳转执行
相关推荐
码匠许师傅1 小时前
【C++三方组件】开篇总论:为什么需要以及如何选型?
开发语言·c++
h_a_o777oah1 小时前
【Games101】C++ 软光追:光线追踪的求交逻辑和 BVH 提效实现及代码实现细节
c++·计算机图形学·光线追踪·games101·bvh·求交算法·软件渲染
cvby1 小时前
C++11--右值引用和移动语义
开发语言·c++
xiangyun612 小时前
【408数据结构 03】线性表与顺序表:C++手写SeqList
开发语言·数据结构·c++
xxxiugou1233 小时前
双指针解题秘籍:从入门到精通
c语言·数据结构·c++·算法
代码中介商3 小时前
C++ 预约系统实战(一):项目总览与 C/S 架构设计
c语言·开发语言·c++
云边有个稻草人4 小时前
Tera Term 鸿蒙 PC 适配全记录:用 ArkUI 与 Native C++ 重建多协议终端工作流
c++·stm32·harmonyos
牛油果子哥q4 小时前
多模态大模型工程入门:图文Embedding、图文RAG、图片解析、C++多模态接口封装实战
开发语言·c++·embedding
6Hzlia5 小时前
【Classic 150 刷题计划】 LeetCode 134. 加油站 | C++ 贪心算法与局部否决逻辑
c++·leetcode·贪心算法