【C/C++】多继承以及继承过程的注意事项
是的,C++ 支持多继承(Multiple Inheritance),包括从多个虚拟类(抽象类)继承。
1. 基本的多继承语法
cpp
class Base1 {
public:
virtual void func1() = 0; // 纯虚函数
};
class Base2 {
public:
virtual void func2() = 0; // 纯虚函数
};
class Derived : public Base1, public Base2 {
public:
void func1() override { /* 实现 */ }
void func2() override { /* 实现 */ }
};
2. 虚拟继承(解决菱形继承问题)
当出现菱形继承时,需要使用虚拟继承来避免数据重复:
cpp
class Base {
public:
int value;
};
class Intermediate1 : virtual public Base {
// 虚拟继承
};
class Intermediate2 : virtual public Base {
// 虚拟继承
};
class Final : public Intermediate1, public Intermediate2 {
// 只有一个 Base 实例
};
int main() {
Final obj;
obj.value = 10; // 没有歧义,只有一个 Base::value
}
3. 多继承的注意事项
a) 函数名冲突
cpp
class A {
public:
void foo() { }
};
class B {
public:
void foo() { } // 同名函数
};
class C : public A, public B {
public:
// 需要明确指定使用哪个父类的 foo
void callFoo() {
A::foo(); // 明确调用 A 的 foo
B::foo(); // 明确调用 B 的 foo
}
};
b) 构造函数调用顺序
cpp
class Base1 {
public:
Base1() { cout << "Base1\n"; }
};
class Base2 {
public:
Base2() { cout << "Base2\n"; }
};
class Derived : public Base1, public Base2 {
public:
Derived() : Base2(), Base1() { // 初始化列表顺序不影响实际构造顺序
cout << "Derived\n";
}
// 实际构造顺序:Base1 → Base2 → Derived
};
4. 接口类(推荐用法)
在实际开发中,通常建议使用纯抽象类作为接口进行多继承:
cpp
// 接口类(只有纯虚函数)
class IReader {
public:
virtual void read() = 0;
virtual ~IReader() = default;
};
class IWriter {
public:
virtual void write() = 0;
virtual ~IWriter() = default;
};
// 实现类
class FileIO : public IReader, public IWriter {
public:
void read() override { /* 实现 */ }
void write() override { /* 实现 */ }
};
5. 多继承的优缺点
优点:
-
提高代码复用性
-
更灵活地组合功能
-
支持接口分离
缺点:
-
增加复杂性
-
可能导致菱形继承问题
-
虚函数表更复杂
建议
-
优先使用组合而非继承
-
如果需要多继承,优先继承接口(纯抽象类)
-
使用虚拟继承解决菱形继承问题
-
明确解决函数名冲突
C++ 标准库中的 iostream 就是多继承的例子:
cpp
class iostream : public istream, public ostream {
// 同时继承输入流和输出流
};
总之,C++ 完全支持从多个虚拟类(抽象类)继承,但需要谨慎使用,以避免设计上的复杂性。
优先使用组合而非继承是什么意思?
“优先使用组合而非继承” 是面向对象设计的一个重要原则,意思是在设计类之间的关系时,应该优先考虑使用组合(composition),而不是继承(inheritance)。
1. 什么是组合?什么是继承?
继承(is-a 关系)
cpp
// "Dog 是一个 Animal"
class Animal {
public:
virtual void makeSound() = 0;
};
class Dog : public Animal { // 继承
public:
void makeSound() override {
cout << "Woof!" << endl;
}
};
组合(has-a 关系)
cpp
// "Car 有一个 Engine"
class Engine {
public:
void start() {
cout << "Engine started" << endl;
}
};
class Car {
private:
Engine engine; // 组合:Car 包含 Engine 对象
public:
void startCar() {
engine.start(); // 委托给 Engine
}
};
2. 为什么优先使用组合?
示例对比:继承的问题
继承方式(不灵活):
cpp
class Bird {
public:
virtual void fly() {
cout << "Flying" << endl;
}
virtual void eat() {
cout << "Eating" << endl;
}
};
class Penguin : public Bird {
public:
void fly() override {
throw runtime_error("Penguins can't fly!"); // 问题:企鹅不会飞!
}
};
组合方式(更灵活):
cpp
// 将功能分解为独立的类
class FlyBehavior {
public:
virtual void fly() = 0;
};
class EatBehavior {
public:
virtual void eat() = 0;
};
// 具体行为实现
class CanFly : public FlyBehavior {
public:
void fly() override {
cout << "Flying" << endl;
}
};
class CannotFly : public FlyBehavior {
public:
void fly() override {
cout << "Cannot fly" << endl;
}
};
// 鸟类使用组合
class Bird {
private:
unique_ptr<FlyBehavior> flyBehavior;
unique_ptr<EatBehavior> eatBehavior;
public:
Bird(unique_ptr<FlyBehavior> fb, unique_ptr<EatBehavior> eb)
: flyBehavior(move(fb)), eatBehavior(move(eb)) {}
void performFly() {
flyBehavior->fly();
}
void performEat() {
eatBehavior->eat();
}
// 可以动态改变行为
void setFlyBehavior(unique_ptr<FlyBehavior> fb) {
flyBehavior = move(fb);
}
};
// 使用
int main() {
// 燕子:会飞
Bird swallow(make_unique<CanFly>(), make_unique<NormalEat>());
// 企鹅:不会飞
Bird penguin(make_unique<CannotFly>(), make_unique<NormalEat>());
// 动态改变行为(继承做不到这点)
penguin.setFlyBehavior(make_unique<CanFly>()); // 现在企鹅会飞了!
}
3. 组合的优势
a) 灵活性更高
cpp
// 继承:编译时绑定
class BasicReport : public Report { /* 固定的功能 */ }
// 组合:运行时可以改变
class Report {
private:
unique_ptr<Formatter> formatter;
unique_ptr<Exporter> exporter;
public:
void setFormatter(unique_ptr<Formatter> fmt) {
formatter = move(fmt);
}
// 可以随时切换为 JSONFormatter、XMLFormatter 等
};
b) 避免脆弱的基类问题
cpp
// 基类修改可能破坏所有子类
class Base {
public:
void foo() { /* ... */ }
};
// 如果 Base::foo() 实现改变,可能影响 Derived
class Derived : public Base {
// 依赖 Base 的实现细节
};
c) 更好的封装性
cpp
class Car {
private:
Engine engine; // 实现细节被隐藏
Transmission trans;
BrakeSystem brakes;
public:
void drive() {
engine.start();
trans.shift();
// 用户不需要知道内部细节
}
};
4. 何时使用继承?
继承仍有其适用场景:
cpp
// 适用场景1:真正的 is-a 关系,且不会改变
class Shape { // 抽象基类
public:
virtual double area() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape { // Circle 就是一个 Shape
public:
double area() const override {
return 3.14 * radius * radius;
}
};
// 适用场景2:需要多态
vector<unique_ptr<Shape>> shapes;
shapes.push_back(make_unique<Circle>());
shapes.push_back(make_unique<Rectangle>());
for (auto& shape : shapes) {
cout << shape->area() << endl; // 多态调用
}
5. 设计原则总结
继承使用场景:
-
明确的 is-a 关系(企鹅是鸟 ×,正方形是形状 ✓)
-
需要多态行为
-
基类是稳定的抽象(不会频繁修改)
组合使用场景:
-
has-a 或 uses-a 关系(汽车有引擎)
-
需要运行时灵活性
-
需要复用多个类的功能
-
避免类层次过深
实际建议:
cpp
// 不好的设计:过深的继承层次
class Vehicle {};
class LandVehicle : public Vehicle {};
class Car : public LandVehicle {};
class Sedan : public Car {};
class SportsSedan : public Sedan {};
// 更好的设计:组合 + 浅继承
class Vehicle {
private:
unique_ptr<Engine> engine;
unique_ptr<Transmission> transmission;
vector<unique_ptr<Feature>> features; // 功能特性
};
// 通过添加不同组件来创建不同车型
class CarFactory {
public:
static Vehicle createSportsCar() {
Vehicle v;
v.setEngine(make_unique<V8Engine>());
v.addFeature(make_unique<SportsSuspension>());
return v;
}
};
6. 实际应用:策略模式
cpp
// 使用组合实现策略模式
class PaymentStrategy {
public:
virtual void pay(double amount) = 0;
virtual ~PaymentStrategy() = default;
};
class CreditCardPayment : public PaymentStrategy {
public:
void pay(double amount) override { /* 信用卡支付逻辑 */ }
};
class PayPalPayment : public PaymentStrategy {
public:
void pay(double amount) override { /* PayPal 支付逻辑 */ }
};
class ShoppingCart {
private:
vector<Item> items;
unique_ptr<PaymentStrategy> paymentStrategy;
public:
void setPaymentStrategy(unique_ptr<PaymentStrategy> strategy) {
paymentStrategy = move(strategy);
}
void checkout() {
double total = calculateTotal();
paymentStrategy->pay(total); // 使用组合的策略
}
};
总结
"优先使用组合而非继承" 的核心思想是:
-
继承表示"是什么"(is-a),在编译时确定关系
-
组合表示"有什么"(has-a),在运行时更灵活
-
组合降低耦合度,提高代码复用性和可维护性
-
只有在真正需要多态和代码共享,且关系稳定时,才使用继承
这个原则帮助创建更灵活、更易维护的系统设计。

浙公网安备 33010602011771号