C++虚继承
虚继承(Virtual Inheritance)
为了解决多继承时的命名冲突和冗余数据问题,c++提出了虚继承,使得在派生类中只保留一份间接基类的成员。
在继承方式前面加上 virtual 关键字就是虚继承
虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class)
虚继承的一个不太直观的特征:必须在虚派生的真实需求出现前就已经完成虚派生的操作.
图中,当定义 D 类时才出现了对虚派生的需求,但是如果 B 类和 C 类不是从 A 类虚派生得到的,那么 D 类还是会保留 A 类的两份成员。
虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。
虚基类成员的可见性
因为在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性。此外,如果虚基类的成员只被一条派生路径覆盖,那么仍然可以直接访问这个被覆盖的成员。但是如果该成员被两条或多条路径覆盖了,那就不能直接访问了,此时必须指明该成员属于哪个类。
以图中的菱形继承为例,假设 A 定义了一个名为 x 的成员变量,当我们在 D 中直接访问 x 时,会有三种可能性:
- 如果 B 和 C 中都没有 x 的定义,那么 x 将被解析为 A 的成员,此时不存在二义性。
- 如果 B 或 C 其中的一个类定义了 x,也不会有二义性,派生类的 x 比虚基类的 x 优先级更高。
- 如果 B 和 C 中都定义了 x,那么直接访问 x 将产生二义性问题
C++虚继承时的构造函数
虚继承中,虚基类是由最终的派生类初始化的,换句话说,最终派生类的构造函数必须要调用虚基类的构造函数。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。
#include <iostream>
using namespace std;
//虚基类A
class A{
public:
A(int a);
protected:
int m_a;
};
A::A(int a): m_a(a){ }
//直接派生类B
class B: virtual public A{
public:
B(int a, int b);
public:
void display();
protected:
int m_b;
};
B::B(int a, int b): A(a), m_b(b){ }
void B::display(){
cout<<"m_a="<<m_a<<", m_b="<<m_b<<endl;
}
//直接派生类C
class C: virtual public A{
public:
C(int a, int c);
public:
void display();
protected:
int m_c;
};
C::C(int a, int c): A(a), m_c(c){ }
void C::display(){
cout<<"m_a="<<m_a<<", m_c="<<m_c<<endl;
}
//间接派生类D
class D: public B, public C{
public:
D(int a, int b, int c, int d);
public:
void display();
private:
int m_d;
};
D::D(int a, int b, int c, int d): A(a), B(90, b), C(100, c), m_d(d){ }
void D::display(){
cout<<"m_a="<<m_a<<", m_b="<<m_b<<", m_c="<<m_c<<", m_d="<<m_d<<endl;
}
int main(){
B b(10, 20);
b.display();
C c(30, 40);
c.display();
D d(50, 60, 70, 80);
d.display();
return 0;
}
结果
m_a=10, m_b=20 m_a=30, m_c=40 m_a=50, m_b=60, m_c=70, m_d=80
以往的普通继承中,派生类的构造函数只负责初始化它的直接基类,再由直接基类的构造函数初始化间接基类,用户尝试调用间接基类的构造函数将导致错误。
现在采用了虚继承,虚基类 A 在最终派生类 D 中只保留了一份成员变量 m_a,如果由 B 和 C 初始化 m_a,那么 B 和 C 在调用 A 的构造函数时很有可能给出不同的实参,这个时候编译器就会犯迷糊,不知道使用哪个实参初始化 m_a。
为了避免出现这种矛盾的情况,c++干脆规定必须由最终的派生类 D 来初始化虚基类 A,直接派生类 B 和 C 对 A 的构造函数的调用是无效的。在第 50 行代码中,调用 B 的构造函数时试图将 m_a 初始化为 90,调用 C 的构造函数时试图将 m_a 初始化为 100,但是输出结果有力地证明了这些都是无效的,m_a 最终被初始化为 50,这正是在 D 中直接调用 A 的构造函数的结果。
虚继承时构造函数的执行顺序与普通继承时不同:在最终派生类的构造函数调用列表中,不管各个构造函数出现的顺序如何,编译器总是先调用虚基类的构造函数,再按照出现的顺序调用其他的构造函数;而对于普通继承,就是按照构造函数出现的顺序依次调用的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了