【C++】虚继承(virtual base classes)

【C++】虚继承(virtual base classes)

文章目录

  • [【C++】虚继承(virtual base classes)](#【C++】虚继承(virtual base classes))
    • [1. 使用原因](#1. 使用原因)
    • [2. 解决方法](#2. 解决方法)
    • [3. 例题练习](#3. 例题练习)

1. 使用原因

在多重继承(Multiple Inheritance) 的情况下,尤其是菱形继承时,容易出现问题,关于菱形继承见下图:

这个类 A 由另外两个类 B 和 C 继承。这两个类都继承到另一个新类 D 中

从图中我们可以看出,类 A 的数据/成员函数被两次继承到类 D。一个通过 B 类继承,另一个通过 C 类继承。当类 A 的任意数据/成员函数被类 D 的对象访问时,对于将调用哪个数据/成员函数会产生歧义,是调用通过 B 继承的,还是调用通过 C 继承的。这会混淆编译器并显示错误。

举个具体的例子

已知类 B 和 C 继承自 A 类,在 A 类中有变量 a

cpp 复制代码
class A
{
public:
	int a;
};

在 B 中定义获取 a 的方法 Get, C 中定义设置 a 的方法 Set,

cpp 复制代码
class B : public A
{
public:
	 int Get() { return a; }
};

class C : public A
{
public:
	void Set(int val) { a = val; }
};

此时 D 类 public 继承 B 和 C

cpp 复制代码
class D : public B, public C
{};

那么使用 D 实例化的对象 dobject,调用 Get 和 Set 接口,所操作的变量 a 为同一个吗?

cpp 复制代码
#include <iostream>
int main(int argc, char* argv[])
{
	D dobject;
	d.Set(10);
	std::cout << d.Get() << std::endl;// undefined value
	
	return 0;
}

答案是否定的,B,C 继承 A,则 B,C 中均有一个 a 变量, B 中的 Get 只能操作 B 中的 a, C 中的 Set 只能操作 C 中的 a ,D 同时拥有了B 和 C 的所有属性,这就是问题所在了。

2. 解决方法

在类 B C 继承类 A 时,使用虚继承 (virtual inheritance),或者叫虚基类 (virtual base classes) ,继承时额外使用 virtual 关键字

虚继承的语法为

cpp 复制代码
class Derived : public virtual Base
{
};
// 或
class Derived : virtual public Base
{
};

virtual 关键字和继承限定符(public,protected,private) 谁在前,谁在后都可以。

也就是:

cpp 复制代码
class B : public virtual A
{
public:
	int Get() { return a; }
};

class C : virtual public A
{
public:
	void Set(int val) { a = val };
};

现在,只有数据/成员函数的一个副本将被复制到类 C 和类 B 中, 类 A 成为虚基类 (virtual base class)。虚基类提供了一种节省空间并避免使用多个继承的类层次结构中出现歧义的方法。将基类指定为虚拟基类时,它可以多次充当间接基类,而不会重复其数据成员。其数据成员的单个副本由使用虚拟基的所有基类共享。

此时 D 再正常继承 B,C 则不会出现问题,

在构建 D 类对象时,构建 B 和 C 时,不构建 B 和 C 类对象中的 A,只构建一次 A ,则在 D 的对象中只存在一个 a 变量

cpp 复制代码
class D : public B, public C
{};
cpp 复制代码
#include <iostream>
int main(int argc, char* argv[])
{
	D dobject;
	d.Set(10);
	std::cout << d.Get() << std::endl;// 10
	
	return 0;
}

3. 例题练习

我们可以看一下菱形继承虚继承 具体是如何实例化的

cpp 复制代码
#include <iostream>

class A {
public:
  A(const char* s) 
  { std::cout << s << std::endl; }
};

class B : public virtual A {
public:
    B(const char* s1, const char* s2) : A(s1)
    { std::cout << s2 << std::endl; }
};

class C : public virtual A {
public:
    C(const char* s1, const char* s2) : A(s1) 
    { std::cout << s2 << std::endl; }
};

class D : public B, C {
public:
    D(const char* s1, const char* s2, const char* s3, const char* s4) 
    : B(s1, s2), C(s3, s4), A(s1)
    {
        std::cout << s4 << std::endl;
    }
};

int main(int argc, char* argv[])
{
    D dobject("class A", "class B", "class C", "class D");

	return 0;
}

由于B,C 为虚继承,实例化 B, C 对象时不会构造 A ,则打印为

cmd 复制代码
class A
class B
class D
class D

参考链接

相关推荐
-To be number.wan11 小时前
C++ 赋值运算符重载:深拷贝 vs 浅拷贝的生死线!
前端·c++
疯狂的挖掘机11 小时前
记一次基于QT的图片操作处理优化思路(包括在图上放大缩小,截图,画线,取值等)
开发语言·数据库·qt
cnxy18811 小时前
围棋对弈Python程序开发完整指南:步骤4 - 提子逻辑和劫争规则实现
开发语言·python·机器学习
意趣新12 小时前
C 语言源文件从编写完成到最终生成可执行文件的完整、详细过程
c语言·开发语言
李艺为12 小时前
根据apk包名动态修改Android品牌与型号
android·开发语言
XXYBMOOO13 小时前
内核驱动开发与用户级驱动开发:深度对比与应用场景解析
linux·c++·驱动开发·嵌入式硬件·fpga开发·硬件工程
黄河滴滴13 小时前
java系统变卡变慢的原因是什么?从oom的角度分析
java·开发语言
老华带你飞13 小时前
农产品销售管理|基于java + vue农产品销售管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
superman超哥13 小时前
Rust Workspace 多项目管理:单体仓库的优雅组织
开发语言·rust·多项目管理·rust workspace·单体仓库
kylezhao201914 小时前
C#通过HSLCommunication库操作PLC用法
开发语言·c#