在C++中,虚继承(Virtual Inheritance)是一种特殊的继承方式,用于解决菱形继承(Diamond Inheritance)问题。菱形继承指的是一个类同时继承自两个或更多个具有共同基类的类,从而导致了多个实例同一个基类的实例的冗余。
多继承-菱形继承
现在来思考以下经典的菱形继承问题:
Animal / \ cat bear \ / panda
在这个继承结构中,类 Animal
是基类,类 cat
和 bear
都直接继承自 Animal
,而类 panda
继承自 cat
和 bear
。当使用传统的继承方式时,panda
类将会继承两份 Animal
类的实例,这会导致冗余。
以下代码就是典型的菱形继承的例子:
cpp
class Animal
{
public:
Animal();
~Animal();
int m_nAge;
};
class Cat : public Animal
{
public:
Cat();
~Cat();
};
class bear:public Animal
{
public:
bear();
~bear();
};
class Panda:public Cat,public bear
{
public:
Panda();
~Panda();
};
Animal
类 :Animal
类是一个基类,具有一个 m_nAge
成员变量,用于表示动物的年龄。
Cat
类和 bear
类 :Cat
类和 bear
类分别是 Animal
类的公有派生类。它们继承了 Animal
类的成员变量和方法。
Panda
类 :Panda
类同时公有派生自 Cat
类和 bear
类,从而继承了这两个类的成员。由于 Cat
类和 bear
类都是从 Animal
类继承而来的,因此 Panda
类实际上包含了两份 Animal
类的成员变量 m_nAge
,这导致冗余数据。
在构造函数中设置标志,创建panda对象后得到的结果:创建一个Panda对象,Animal构造了两次。
此时我在主程序中进行panda对象的创建,并尝试对panda继承下来的的m_nAge
对象进行赋值:
此时程序报错,并提示Panda
的m_nAge
成员不明确,原因其实就是因为Panda
继承自Cat
和bear
两个类,那么就意味着Panda
类继承了两份 Animal
类的成员变量 m_nAge
,此时使用panda->m_nAge = 10;
对Panda
的变量进行赋值编译器就会混淆;如果不想程序爆红就在赋值时就必须成员变量的继承来源:
cpp
int main() {
Panda * panda = new Panda;
//成员变量的赋值
panda->Cat::m_nAge = 10;
panda->bear::m_nAge = 15;
std::cout << "Panda Age:" << panda->Cat::m_nAge << " Panda AgeB:" << panda->bear::m_nAge << std::endl;
delete panda;
system("pause");
return 0;
}
panda->Cat::m_nAge = 10;
:给 panda
对象中 Cat
类的成员变量 m_nAge
赋值为 10
。
panda->bear::m_nAge = 15;
:给 panda
对象中 bear
类的成员变量 m_nAge
赋值为 15
。
就算解决了程序不报错的问题其实菱形继承(多继承)时还会出现其他问题:
1.数据冗余:每个派生类实例都会包含基类实例的副本,这可能会导致内存空间的浪费,特别是在多重继承的情况下,可能会存在多份相同的基类数据。 2.数据一致性:如果多个派生类实例共享同一个基类实例,那么当修改其中一个派生类实例中的基类数据时,会影响到其他共享同一基类实例的派生类实例,这可能会导致数据不一致的问题。 3.维护困难:当多个派生类实例共享同一个基类实例时,可能会增加代码的复杂性和维护成本,因为需要确保在任何修改基类数据的地方都能正确地处理多个派生类实例。 4.生命周期问题:如果派生类实例在不同的时间点创建和销毁,而共享的基类实例在某个派生类实例被销毁后还继续存在,这可能会导致悬挂指针或内存泄漏等问题
这个时候就可以使用虚继承对以上问题进行统一解决;
虚继承
虚继承通过使用关键字 virtual
来解决这个问题。当一个类以虚继承方式继承自一个基类时,无论该类被多次继承,其基类的实例只会在继承层次结构中的最顶层被创建一次。这意味着,对于上面的菱形继承问题,使用虚继承后,D
类将只继承一份 A
类的实例。
回到上述例子:
cpp
class Animal
{
public:
Animal();
~Animal();
int m_nAge;
}
//使用虚继承
class Cat : public virtual Animal
{
public:
Cat();
~Cat();
};
//使用虚继承
class bear:public virtual Animal
{
public:
bear();
~bear();
};
class Panda:public Cat,public bear
{
public:
Panda();
~Panda();
};
Cat
类和 bear
类 :这两个类都使用了虚继承,即使用 virtual
关键字来继承自 Animal
类。这意味着无论 Cat
类和 bear
类被多次继承,Animal
类的实例都只会在继承层次结构中的最顶层被创建一次。这样可以避免在 Panda
类中出现多个 Animal
类的实例,从而解决了菱形继承问题。
Panda
类 :Panda
类同时公有派生自 Cat
类和 bear
类,从而继承了这两个类的成员。因为 Cat
类和 bear
类都是虚继承自 Animal
类的,所以 Panda
类中只包含了一个 Animal
类的实例,从而避免了多个实例共享同一个基类实例的冗余问题。
这就意味着此时我们在主函数中创建panda对象,并对对象的m_nAge
成员进行赋值/访问:
cpp
int main() {
Panda * panda = new Panda;
panda->m_nAge = 10;
std::cout << "Panda Age:" << panda->m_nAge << std::endl;
delete panda;
}
运行结果:
cpp
Panda Age:10
panda
对象中的m_nAge
成员变量就只有一个副本,这个时候就对对象中的成员进行访问时就无需再指定继承路径了。