【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. 多继承的优缺点

优点:

  • 提高代码复用性

  • 更灵活地组合功能

  • 支持接口分离

缺点:

  • 增加复杂性

  • 可能导致菱形继承问题

  • 虚函数表更复杂

建议

  1. 优先使用组合而非继承

  2. 如果需要多继承,优先继承接口(纯抽象类)

  3. 使用虚拟继承解决菱形继承问题

  4. 明确解决函数名冲突

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. 设计原则总结

继承使用场景:

  1. 明确的 is-a 关系(企鹅是鸟 ×,正方形是形状 ✓)

  2. 需要多态行为

  3. 基类是稳定的抽象(不会频繁修改)

组合使用场景:

  1. has-a 或 uses-a 关系(汽车有引擎)

  2. 需要运行时灵活性

  3. 需要复用多个类的功能

  4. 避免类层次过深

实际建议:

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),在运行时更灵活

  • 组合降低耦合度,提高代码复用性和可维护性

  • 只有在真正需要多态代码共享,且关系稳定时,才使用继承

这个原则帮助创建更灵活、更易维护的系统设计。

posted @ 2025-12-12 00:24  FBshark  阅读(10)  评论(0)    收藏  举报