当前位置: 首页 > news >正文

Effective C++ 条款32:确定你的public继承塑模出 is-a 关系

Effective C++ 条款32:确定你的public继承塑模出is-a关系


核心思想public继承必须严格遵循"is-a"关系(里氏替换原则),即派生类对象在任何场景下都必须是基类对象的逻辑子集。违反这一原则将导致设计错误和运行时异常。

⚠️ 1. is-a关系的内涵与要求

基本原则

  • 派生类对象可被当作基类对象使用
  • 基类的所有行为都适用于派生类
  • 派生类可扩展功能,但不能削弱基类契约

数学表达

∀x (x∈Derived → x∈Base)   // 派生类是基类的子集

代码验证

void process(const Base& b); // 接受基类的函数Derived d;
process(d); // 必须能正确工作(里氏替换)

违反示例

class Bird {
public:virtual void fly(); // 鸟会飞
};class Penguin : public Bird { // 企鹅是鸟,但不会飞!
public:void fly() override { throw CantFlyException(); // 违反is-a!}
};Penguin p;
processBird(p); // 可能抛出异常,破坏基类契约

🚨 2. 常见设计陷阱与解决方案

陷阱1:非普适行为继承

// 错误设计
class Rectangle {
public:virtual void setHeight(int h); // 可独立设置宽高
};class Square : public Rectangle { 
public:void setHeight(int h) override {width_ = h; // 同时修改宽度height_ = h;}// 违反矩形行为契约!
};void stretch(Rectangle& r) {r.setHeight(r.getHeight() + 10); // 对正方形会意外修改宽度
}

解决方案

// 正确设计:不继承
class Square {
public:void setSide(int s) { ... }
};// 关系建模
class Shape { /* 公共接口 */ };
class Rectangle : public Shape { ... };
class Square : public Shape { ... };

陷阱2:接口污染

class Airport { ... };// 错误设计
class Aircraft {
public:virtual void takeoff(Airport& dest) = 0;
};class HeliPad; // 直升机专用class Helicopter : public Aircraft {
public:void takeoff(HeliPad& pad); // 参数类型不兼容!
};

解决方案

// 正确设计:分离接口
class Aircraft {
public:virtual void takeoff() = 0;
};class CommercialAircraft : public Aircraft {
public:void setDestination(Airport& ap);void takeoff() override;
};class Helicopter : public Aircraft {
public:void setLaunchPad(HeliPad& pad);void takeoff() override;
};

⚖️ 3. 最佳实践指南
场景推荐方案原因
严格is-a关系✅ 使用public继承天然建模分类关系
共享接口但行为不同🔶 接口继承+实现覆盖保持多态行为一致
“has-a"或"is-implemented-in-terms-of”⛔ 避免继承,用组合防止接口污染
运行时类型依赖⚠️ 避免dynamic_cast通常是设计缺陷的信号
接口扩展✅ 非虚拟接口模式(NVI)保持核心策略稳定

现代C++增强

// 明确禁止覆盖(C++11)
class Base {
public:virtual void stableAPI() final; // 禁止派生类修改
};// 显式重写语法(C++11)
class Derived : public Base {
public:void stableAPI() override; // 错误!final函数不能override
};// 契约编程(C++20)
class Shape {
public:virtual void draw() [[expects: isValid()]]  // 前置条件[[ensures: isDrawn()]]  // 后置条件= 0;
};

💡 关键设计原则

  1. 里氏替换测试

    • 派生类对象必须能替代基类对象
    • 所有基类操作在派生类中保持语义一致
    • 派生类不强化前置条件/不弱化后置条件
  2. 契约继承优先

    • 继承行为契约而非具体实现
    • 使用纯虚函数定义严格接口
    • 模板方法模式控制流程
  3. 组合优于继承

    // "has-a"关系使用组合
    class Car {
    private:Engine engine_;  // Car has-a Engine
    };// "is-implemented-in-terms-of"使用组合
    class Set {
    private:std::list<int> impl_; // 用list实现set
    public:void insert(int v) {if(!contains(v)) impl_.push_back(v);}
    };
    
  4. 类型特征检查

    // C++17编译时检查
    template<typename T>
    void process(T obj) {static_assert(std::is_base_of_v<Base, T>, "T must inherit from Base");// ...
    }
    

危险模式重现

class Database {
public:virtual void open() = 0;
};class MySQL : public Database {
public:void open() override; // 需要连接参数?
};void runReport(Database& db) {db.open(); // 对MySQL可能缺少必要参数
}

安全重构方案

class Database {
public:void open(const ConnectionParams& params) { // 非虚接口validate(params);doOpen(params); // 虚函数}
private:virtual void doOpen(const ConnectionParams&) = 0;
};class MySQL : public Database {
private:void doOpen(const ConnectionParams& params) override {// 使用参数建立连接}
};

多态安全场景

class Animal {
public:virtual void move() = 0;
};class Bird : public Animal {
public:void move() override { fly(); }virtual void fly() { /* 飞行实现 */ }
};class Ostrich : public Bird { // 鸵鸟是鸟但不会飞
public:void fly() override {throw CannotFlyError(); // 设计错误!}
};// 正确设计:分离会飞行为
class FlyingAnimal : public Animal {
public:void move() override { fly(); }virtual void fly() = 0;
};class Bird : public Animal { /* 不强制飞行 */ };
class Eagle : public Bird, public FlyingAnimal { ... };
class Ostrich : public Bird { /* 实现行走 */ };
http://www.lryc.cn/news/615528.html

相关文章:

  • pytorch+tensorboard+可视化CNN
  • ubuntu dpkg命令使用指南
  • 【线性代数】其他
  • 机器翻译实战:使用Gensim训练中英文词向量模型及可视化
  • leetcode-C语言-3479.水果成篮 III
  • 写 SPSS文件系统
  • Linux软件编程:shell
  • 组合期权:垂直价差
  • C++ 中的智能指针
  • 电子电气架构 --- 电气/电子架构迁移已拉开帷幕
  • Oracle数据库重启后打开异常状态的检查步骤
  • 一周学会Matplotlib3 Python 数据可视化-网格 (Grid)
  • [IOMMU]面向芯片/SoC验证工程的IOMMU全景速览
  • C# 通过第三方库INIFileParser管理INI配置文件
  • 智慧园区误报率↓76%:陌讯多模态融合算法实战解析
  • 202506 电子学会青少年等级考试机器人一级理论综合真题
  • 闲鱼智能监控机器人:基于 Playwright 与 AI 的多任务监控分析工具
  • 2025年SEVC SCI2区,基于深度强化学习与模拟退火的多无人机侦察任务规划,深度解析+性能实测
  • Dify 从入门到精通(第 24/100 篇):Dify 的实时数据处理与流式输出
  • 微积分 | 外微分
  • HUAWEI交换机命令基础
  • java基础(六)jvm
  • 微信小程序中实现表单自动填充功能的方法
  • Linux网络子系统架构分析
  • P1025 [NOIP 2001 提高组] 数的划分 题解
  • 基于麦克风阵列电机噪声振动监测解决方案技术解析
  • “自动报社保 + 查询导出 ” 的完整架构图和 Playwright C# 项目初始化模板
  • BroadcastChannel:轻松实现前端跨页面通信
  • 06-docker容器常用命令
  • 全栈:JDBC驱动版本和SQLserver版本是否有关系?怎么选择JDBC的版本号?