C++的虚继承实现示例

 更新时间:2026年02月03日 09:41:13   作者:Ralph_Y  
虚继承是C++中解决多重继承中菱形继承问题的专属机制,通过让共同基类成为虚基类,保证其在最终派生类中仅存在一份实例,消除数据冗余和访问二义性,下面就来介绍一下C++ 虚继承的使用

一、虚继承的核心定位:解决菱形继承的痛点

在讲解虚继承前,先明确其诞生的背景——菱形继承(钻石继承) 是多重继承的典型问题,而虚继承是C++专门设计的解决方案:

  • 菱形继承:多个基类继承自同一个“共同基类”,最终派生类又同时继承这些基类,导致共同基类的成员在最终派生类中存在多份副本(数据冗余),访问时引发二义性
  • 虚继承:通过virtual关键字声明继承,让“共同基类”成为虚基类,使其在最终派生类中仅保留一份实例,彻底解决数据冗余和二义性。

二、虚继承的基本语法与核心概念

1. 语法格式

虚继承的关键字virtual需加在“继承方式”前,修饰的是“继承行为”,而非基类本身:

// 格式:class 派生类 : virtual 继承方式 虚基类 { ... };
class 中间基类1 : virtual public 共同基类 { ... };
class 中间基类2 : virtual public 共同基类 { ... };
class 最终派生类 : public 中间基类1, public 中间基类2 { ... };
  • 虚基类:被virtual继承的“共同基类”(比如下面示例中的Animal);
  • 最终派生类:菱形结构最底层的类(比如下面示例中的Duck),是唯一负责初始化虚基类的类。

2. 先看问题:普通菱形继承的坑

先通过代码复现菱形继承的核心问题(数据冗余+二义性),让你直观感受为什么需要虚继承:

#include <iostream>
using namespace std;

// 共同基类:Animal
class Animal {
public:
    int age;
    Animal(int a) : age(a) {
        cout << "Animal构造,age=" << a << endl;
    }
};

// 中间基类1:Flyable(普通继承Animal)
class Flyable : public Animal {
public:
    Flyable(int a) : Animal(a) {} // 初始化Animal
    void fly() { cout << "能飞,age=" << age << endl; }
};

// 中间基类2:Swimmable(普通继承Animal)
class Swimmable : public Animal {
public:
    Swimmable(int a) : Animal(a) {} // 初始化Animal
    void swim() { cout << "能游泳,age=" << a << age << endl; }
};

// 最终派生类:Duck(多重继承Flyable、Swimmable)
class Duck : public Flyable, public Swimmable {
public:
    // 必须初始化两个中间基类,间接初始化两次Animal
    Duck(int a) : Flyable(a), Swimmable(a) {}
};

int main() {
    Duck duck(2);
    // 问题1:二义性——编译器不知道访问哪一份age
    // cout << duck.age << endl; // 编译报错:ambiguous reference to 'age'
    
    // 问题2:数据冗余——存在两份age,地址不同
    cout << &duck.Flyable::age << endl;  // 0x7ffeefbff5e0
    cout << &duck.Swimmable::age << endl;// 0x7ffeefbff5e4
    return 0;
}

输出(构造阶段)

Animal构造,age=2  // Flyable初始化的Animal
Animal构造,age=2  // Swimmable初始化的Animal

核心问题总结:

  • Duck对象中有两份age,浪费内存;
  • 直接访问age编译报错,必须通过Flyable::Swimmable::限定作用域;
  • Animal被构造了两次,不符合逻辑(一只鸭子只有一个年龄)。

3. 用虚继承解决问题

仅需修改FlyableSwimmable的继承方式,添加virtual关键字:

#include <iostream>
using namespace std;

class Animal {
public:
    int age;
    Animal(int a) : age(a) {
        cout << "Animal构造,age=" << a << endl; // 仅构造一次
    }
};

// 关键修改:virtual public Animal
class Flyable : virtual public Animal {
public:
    Flyable(int a) : Animal(a) {} // 此初始化会被忽略!
};

// 关键修改:virtual public Animal
class Swimmable : virtual public Animal {
public:
    Swimmable(int a) : Animal(a) {} // 此初始化会被忽略!
};

class Duck : public Flyable, public Swimmable {
public:
    // 核心规则:最终派生类必须直接初始化虚基类Animal
    Duck(int a) : Animal(a), Flyable(a), Swimmable(a) {}
};

int main() {
    Duck duck(2);
    // 问题解决1:无二义性,直接访问age
    cout << duck.age << endl; // 输出2
    
    // 问题解决2:数据冗余消除,只有一份age
    cout << &duck.Flyable::age << endl;  // 0x7ffeefbff5e0
    cout << &duck.Swimmable::age << endl;// 0x7ffeefbff5e0(和上面地址相同)
    return 0;
}

输出(构造阶段)

Animal构造,age=2  // 仅构造一次!

核心变化总结:

  • Animal仅被构造一次,Duck中只有一份age
  • 直接访问duck.age无编译错误,二义性彻底解决;
  • 两份age的地址完全相同,证明数据冗余消除。

三、虚继承的核心规则(必须牢记)

1. 虚基类的初始化规则(最关键)

  • 最终派生类负责初始化虚基类:无论中间基类是否写了虚基类的初始化代码,都会被编译器忽略,只有最终派生类的初始化才生效;
  • 若最终派生类未显式初始化虚基类,且虚基类无默认构造函数,编译报错。

2. 虚基类的实例唯一性

  • 虚基类的实例在最终派生类中全局唯一,所有中间基类都共享这一份实例;
  • 即使最终派生类有多层继承,虚基类也仅构造一次。

3. 作用域解析的优先级

  • 若最终派生类有和虚基类同名的成员,直接访问时优先访问最终派生类的成员;
  • 若需访问虚基类的成员,需通过虚基类名::限定。

四、虚继承的底层原理(新手简化版)

虚继承的实现依赖编译器的两个核心机制(无需深入底层,理解概念即可):

  1. 虚基类指针(vbptr):每个虚继承的中间基类(如FlyableSwimmable)会在对象中添加一个隐藏的指针vbptr
  2. 虚基类表(vbtable):编译器为每个虚继承的类生成一张表,记录vbptr到虚基类实例(如Animal)的内存偏移量。

工作流程:当访问duck.Flyable::age时,编译器通过Flyablevbptr找到vbtable,再通过偏移量定位到唯一的Animal::age,从而避免二义性和冗余。

提示:虚继承有轻微的性能开销(指针访问+表查询),但在现代编译器下几乎可以忽略,仅需在“必须解决菱形继承”时使用,不要滥用。

五、虚继承与虚函数的区别(易混淆点)

很多新手会混淆“虚继承”和“虚函数”,核心区别如下:

特性虚继承(virtual inheritance)虚函数(virtual function)
关键字virtual修饰继承行为virtual修饰成员函数
核心目的解决菱形继承的二义性和数据冗余实现运行时多态(动态绑定)
底层实现依赖vbptr + vbtable依赖vptr + vtable
作用范围类的继承体系类的成员函数

六、总结

  1. 核心目的:虚继承是C++解决多重继承中菱形继承问题的专属机制,通过让共同基类成为虚基类,保证其在最终派生类中仅存在一份实例,消除数据冗余和访问二义性;
  2. 核心规则:虚基类的构造函数由最终派生类统一初始化,中间基类对虚基类的初始化会被忽略;
  3. 使用场景:仅在遇到菱形继承时使用,避免滥用(可优先考虑组合/接口替代多重继承);
  4. 易混点:虚继承≠虚函数,前者解决继承冗余,后者实现多态,底层机制不同但都依赖编译器的隐藏指针和表。

到此这篇关于C++的虚继承实现示例的文章就介绍到这了,更多相关C++ 虚继承内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 浅谈C++ Socket编程

    浅谈C++ Socket编程

    本文给大家简单介绍了C++中的Socket编程的种类以及sockets编程的8个步奏,简单生动,有需要的小伙伴可以参考下
    2017-07-07
  • C语言实现从文件读入一个3*3数组,并计算每行的平均值

    C语言实现从文件读入一个3*3数组,并计算每行的平均值

    今天小编就为大家分享一篇C语言实现从文件读入一个3*3数组,并计算每行的平均值,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12
  • C++中的三种继承public,protected,private详细解析

    C++中的三种继承public,protected,private详细解析

    我们已经知道,在基类以private方式被继承时,其public和protected成员在子类中变为private成员。然而某些情况下,需要在子类中将一个或多个继承的成员恢复其在基类中的访问权限
    2013-09-09
  • c++中STL库队列详细介绍

    c++中STL库队列详细介绍

    大家好,本篇文章主要讲的是c++中STL库队列详细介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2021-12-12
  • C++隐式类型转换运算符operator type()用法详解

    C++隐式类型转换运算符operator type()用法详解

    这篇文章主要介绍了C++隐式类型转换运算符operator type()用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • C++二分查找算法实例

    C++二分查找算法实例

    这篇文章主要为大家详细介绍了C++二分查找算法的实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • C语言实现飞机票务系统

    C语言实现飞机票务系统

    这篇文章主要为大家详细介绍了C语言实现飞机票务系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-12-12
  • C++11中std::move、std::forward、左右值引用、移动构造函数的测试问题

    C++11中std::move、std::forward、左右值引用、移动构造函数的测试问题

    这篇文章主要介绍了C++11中std::move、std::forward、左右值引用、移动构造函数的测试,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • 详解c++ atomic原子编程中的Memory Order

    详解c++ atomic原子编程中的Memory Order

    在多核编程中,我们使用内核对象【如:事件对象(Event)、互斥量对象(Mutex,或互斥体对象)、信号量对象(Semaphore)等】来避免多个线程修改同一个数据时产生的竞争条件。本文将详细介绍c++ atomic原子编程中的Memory Order。
    2021-06-06
  • C语言结构体计算内存占用问题解析

    C语言结构体计算内存占用问题解析

    这篇文章主要介绍了C语言结构体计算内存占用问题解析,本文通过案例来解析了C语言计算结构体内存的方式和方法,需要的朋友可以参考下
    2021-07-07

最新评论