一、多重继承(multiple inheritance)
1. 基本概念
多重继承是指一个派生类同时继承多个基类,允许派生类复用多个基类的属性和方法,是c++区别于java等语言的重要特性之一。
语法格式:
class 派生类名 : 继承方式1 基类1, 继承方式2 基类2, ... {
// 派生类成员
};
- 继承方式:
public/protected/private(和单继承规则一致); - 核心价值:让一个类同时拥有多个类的特性(比如“汽车”同时继承“交通工具”和“机械产品”)。
2. 简单示例(无冲突的多重继承)
#include <iostream>
using namespace std;
// 基类1:飞行动物
class flyable {
public:
void fly() {
cout << "能飞" << endl;
}
};
// 基类2:游泳动物
class swimmable {
public:
void swim() {
cout << "能游泳" << endl;
}
};
// 派生类:鸭子(同时继承飞行动物和游泳动物)
class duck : public flyable, public swimmable {
public:
void quack() {
cout << "嘎嘎叫" << endl;
}
};
int main() {
duck duck;
duck.fly(); // 继承自flyable
duck.swim(); // 继承自swimmable
duck.quack(); // 自身方法
return 0;
}
输出:
能飞
能游泳
嘎嘎叫
这个示例中,duck同时复用了flyable和swimmable的方法,无任何冲突,是多重继承的理想场景。
3. 多重继承的核心问题:菱形继承(钻石继承)
当多个基类继承自同一个“共同基类”,而派生类又同时继承这些基类时,会形成菱形结构,导致两个核心问题:
- 数据冗余:共同基类的成员会在派生类中存在多份副本;
- 二义性:访问共同基类的成员时,编译器无法确定访问哪一份副本。
菱形继承示例(有问题):
#include <iostream>
using namespace std;
// 共同基类:动物
class animal {
public:
int age; // 共同成员
animal(int a) : age(a) {}
};
// 基类1:飞行动物(继承animal)
class flyable : public animal {
public:
flyable(int a) : animal(a) {}
void fly() { cout << "能飞,年龄:" << age << endl; }
};
// 基类2:游泳动物(继承animal)
class swimmable : public animal {
public:
swimmable(int a) : animal(a) {}
void swim() { cout << "能游泳,年龄:" << age << endl; }
};
// 派生类:鸭子(同时继承flyable和swimmable)
class duck : public flyable, public swimmable {
public:
// 问题1:构造函数需要初始化两次animal(数据冗余)
duck(int a) : flyable(a), swimmable(a) {}
void quack() { cout << "嘎嘎叫" << endl; }
};
int main() {
duck duck(2);
// 问题2:访问age会触发二义性编译错误
// cout << duck.age << endl; // 报错:ambiguous reference to 'age'
// 强行指定作用域可以访问,但本质是两份age(数据冗余)
cout << duck.flyable::age << endl; // 2
cout << duck.swimmable::age << endl; // 2
return 0;
}
关键问题分析:
- duck对象中存在两份age(分别来自flyable和swimmable),即使初始化值相同,也是独立的内存空间;
- 直接访问duck.age会编译报错,因为编译器不知道你要访问哪一份age;
- 这种冗余不仅浪费内存,还可能导致逻辑错误(比如修改其中一份,另一份不变)。
二、虚继承(virtual inheritance)
1. 基本概念
虚继承是c++为解决菱形继承的二义性和数据冗余设计的机制:通过virtual关键字声明继承,让“共同基类”成为虚基类,此时无论派生多少次,共同基类在最终派生类中只保留一份实例。
语法格式:
class 派生类名 : virtual 继承方式 基类名 {
// 成员
};
virtual关键字加在“继承方式”前;- 核心作用:让共同基类的实例在最终派生类中唯一。
2. 虚继承解决菱形继承的示例
修改上面的代码,给flyable和swimmable的继承加virtual:
#include <iostream>
using namespace std;
// 共同基类:动物
class animal {
public:
int age;
animal(int a) : age(a) {
cout << "animal构造:age=" << a << endl;
}
};
// 虚继承animal:飞行动物
class flyable : virtual public animal {
public:
// 虚继承下,这里的构造函数不会直接初始化animal(由最终派生类统一初始化)
flyable(int a) : animal(a) {}
void fly() { cout << "能飞,年龄:" << age << endl; }
};
// 虚继承animal:游泳动物
class swimmable : virtual public animal {
public:
swimmable(int a) : animal(a) {}
void swim() { cout << "能游泳,年龄:" << age << endl; }
};
// 最终派生类:鸭子
class duck : public flyable, public swimmable {
public:
// 虚继承下,最终派生类必须直接初始化虚基类animal(唯一入口)
duck(int a) : animal(a), flyable(a), swimmable(a) {}
void quack() { cout << "嘎嘎叫" << endl; }
};
int main() {
duck duck(2);
// 无二义性:只有一份age
cout << duck.age << endl; // 输出2
duck.fly(); // 能飞,年龄:2
duck.swim(); // 能游泳,年龄:2
return 0;
}输出:
animal构造:age=2
2
能飞,年龄:2
能游泳,年龄:2
关键变化:
- 数据冗余解决:duck对象中只有一份age,不再有重复副本;
- 二义性解决:直接访问duck.age无编译错误,编译器明确知道访问唯一的age;
- 构造规则变化:虚基类的构造函数由最终派生类统一初始化(即使中间基类写了初始化,也会被忽略),保证虚基类只构造一次。
3. 虚继承的底层原理
虚继承的实现依赖编译器的虚基类表(vbtable):
- 每个虚继承的类会存储一个指向“虚基类表”的指针;
- 虚基类表记录了当前类到虚基类实例的偏移量;
- 通过这个表,所有派生类都能找到唯一的虚基类实例,避免冗余和二义性。
提示:虚继承有轻微的性能开销(指针访问+表查询),因此仅在需要解决菱形继承时使用,不要滥用。
三、总结
- 多重继承:允许一个类继承多个基类,复用多类特性,但会引发菱形继承的数据冗余和二义性问题;
- 虚继承:通过
virtual关键字声明继承,将共同基类变为虚基类,使其在最终派生类中仅保留一份实例,解决菱形继承的核心问题; - 关键规则:虚基类的构造函数由最终派生类统一初始化,中间基类对虚基类的初始化会被编译器忽略。
补充:多重继承的其他注意事项
- 如果多个基类有同名方法,即使不是菱形继承,访问时也需要指定作用域(比如
duck.flyable::func()); - 实际开发中,应尽量避免复杂的多重继承(可通过组合/接口替代),仅在确有必要时使用;
- c++11后的
std::enable_shared_from_this、qt的qobject等经典场景会用到虚继承。
到此这篇关于c++中多重继承与虚继承的实现的文章就介绍到这了,更多相关c++ 多重继承与虚继承内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论