c++的虚继承说明、案例、代码

虚继承的基本概念

  • 在 C++ 中,虚继承主要用于解决多继承时可能出现的菱形继承问题。菱形继承是指一个类有两个(或更多)子类,而这两个子类又同时继承自一个共同的基类,当这些子类又被另一个类继承时,就形成了菱形结构。在这种情况下,如果没有虚继承,会导致基类数据成员在派生类中有多份副本,可能引起二义性等问题。虚继承可以保证在这种复杂的继承关系中,公共基类只有一份副本。

简单的虚继承示例

  • 首先看一个没有虚继承导致数据成员重复的例子:
  • cpp
cpp 复制代码
class Base {
public:
    int baseData;
};

class Derived1 : public Base {
};

class Derived2 : public Base {
};

class GrandDerived : public Derived1, public Derived2 {
};

int main() {
    GrandDerived gd;
    // 下面这行代码会产生二义性错误,因为baseData在Derived1和Derived2中都存在
    // gd.baseData = 10;
    return 0;
}
  • 在这个例子中,GrandDerived类通过Derived1Derived2间接继承了Base类,这就导致GrandDerived对象中有两份Base类的数据成员baseData。当试图访问baseData时会产生二义性错误。

使用虚继承解决菱形继承问题

  • 下面是使用虚继承来解决上述问题的代码
  • cpp
cpp 复制代码
class Base {
public:
    int baseData;
};

class Derived1 : virtual public Base {
};

class Derived2 : virtual public Base {
};

class GrandDerived : public Derived1, public Derived2 {
};

int main() {
    GrandDerived gd;
    gd.baseData = 10;  // 正确,此时只有一份baseData
    return 0;
}
  • 在这个修改后的代码中,Derived1Derived2虚继承自Base类。这使得在GrandDerived类中,Base类只会有一份副本,所以可以正确地访问baseData成员。

虚继承的构造函数顺序案例

  • 当涉及虚继承时,构造函数的调用顺序也有特殊的规则。构造函数的调用顺序是先调用虚基类的构造函数,然后再按照继承顺序调用非虚基类的构造函数。
  • cpp
cpp 复制代码
class Base {
public:
    Base() {
        std::cout << "Base constructor" << std::endl;
    }
};

class Derived1 : virtual public Base {
public:
    Derived1() {
        std::cout << "Derived1 constructor" << std::endl;
    }
};

class Derived2 : virtual public Base {
public:
    Derived2() {
        std::cout << "Derived2 constructor" << std::endl;
    }
};

class GrandDerived : public Derived1, public Derived2 {
public:
    GrandDerived() {
        std::cout << "GrandDerived constructor" << std::endl;
    }
};

int main() {
    GrandDerived gd;
    return 0;
}
  • 在这个例子中,输出结果是:

    Base constructor
    Derived1 constructor
    Derived2 constructor
    GrandDerived constructor

  • 可以看到,首先调用了虚基类Base的构造函数,然后按照继承顺序调用了Derived1Derived2的构造函数,最后调用了GrandDerived的构造函数。

虚继承中的指针和引用案例

  • 考虑以下代码来展示虚继承中指针和引用的行为:
  • cpp
cpp 复制代码
class Base {
public:
    int baseData;
    virtual void print() {
        std::cout << "Base print" << std::endl;
    }
};

class Derived1 : virtual public Base {
public:
    void print() override {
        std::cout << "Derived1 print" << std::endl;
    }
};

class Derived2 : virtual public Base {
public:
    void print() override {
        std::cout << "Derived2 print" << std::endl;
    }
};

class GrandDerived : public Derived1, public Derived2 {
};

int main() {
    GrandDerived gd;
    Base* ptr = &gd;
    ptr->print();  // 调用Derived1的print函数,这取决于继承顺序和虚函数机制
    return 0;
}
  • 在这个例子中,通过Base*指针指向GrandDerived对象,当调用print函数时,由于虚函数的动态绑定特性和继承顺序,实际上调用的是Derived1类中的print函数。这展示了在虚继承场景下,通过基类指针或引用访问虚函数时的多态行为。

以下是用流程图来说明虚继承用于解决多继承时菱形继承问题的过程:

cpp 复制代码
graph TD;
    A[定义基类Base] --> B[定义子类Derived1和Derived2直接继承Base];
    B --> C[定义GrandDerived类继承Derived1和Derived2形成菱形继承结构];
    C --> D[不使用虚继承时,GrandDerived对象中有两份Base类的数据成员,访问可能出现二义性];
    A --> E[定义子类Derived1和Derived2虚继承Base];
    E --> F[定义GrandDerived类继承Derived1和Derived2];
    F --> G[使用虚继承后,Base类在GrandDerived对象中只有一份副本,可正常访问数据成员];

在上述流程图中:

  • 首先是定义一个基类Base
  • 然后有两种情况分支:
    • 一种是常规的非虚继承方式,Derived1Derived2直接继承Base,之后GrandDerived再继承Derived1Derived2,这样会形成菱形继承结构,并且在不使用虚继承时,GrandDerived对象中会存在两份Base类的数据成员,导致在访问这些数据成员时可能出现二义性问题。
    • 另一种是采用虚继承的方式,Derived1Derived2虚继承Base,接着GrandDerived继承Derived1Derived2,此时由于虚继承的作用,Base类在GrandDerived对象中只会有一份副本,从而可以正常地访问数据成员,避免了二义性等问题。
相关推荐
神仙别闹1 小时前
基于 C++ 实现(控制台)学生成绩管理系统
开发语言·c++
会周易的程序员1 小时前
5 节点边缘冗余方案(上):基于 aiRaft 的物联网高可用控制面设计
c++·分布式·物联网·raft·iot·共识
伞伞悦读2 小时前
【第36期】Python 目录与路径详解:pathlib、文件遍历、创建、复制、移动和删除风险
开发语言·python
qq_199886875 小时前
第6板块·第4节:构建系统与多文件项目
c++·人工智能·gpu算力
2601_966949655 小时前
只拿到股票代码还不够:量化程序到底还需要哪些标的信息?——从数据建模开始理解股票元数据
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
m0_734571765 小时前
深入理解5g <十八>传输块tbsize的计算
开发语言·5g
汉克老师6 小时前
GESP2026年9月认证C++三级( 第三部分编程题(2、分割字符串))精讲
c++·gesp·小学生·学c++编程
估值探索者6 小时前
【Python量化系统工程化 #08】关了 SSH 就停?systemd 让脚本开机自启 + 异常自动拉起
java·c++·人工智能·分类·数据挖掘
RAN11426760086 小时前
1.3第一个程序的介绍
开发语言·qt
天若有情6736 小时前
C++花式魔改!手写头文件复刻Java语法,彻底打破编码思维定式
java·c++·语言做语言·jac++