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

Effective C++ 条款34:区分接口继承和实现继承

Effective C++ 条款34:区分接口继承和实现继承


核心思想public继承中,成员函数的不同声明方式对应着不同的继承语义:纯虚函数只继承接口,虚函数继承接口和默认实现,非虚函数继承接口和强制实现。错误混用会导致设计脆弱性。

⚠️ 1. 三种继承方式的本质区别

继承类型对照表

函数类型接口继承实现继承设计意图风险
纯虚函数强制派生类提供实现无默认实现可能代码重复
虚函数✅(可选)提供默认实现,允许派生类覆盖默认实现可能不适用所有派生
非虚函数✅(强制)定义不可更改的通用行为派生类无法定制行为

代码验证

class Shape {
public:// 纯虚函数:只继承接口virtual void draw() const = 0;// 虚函数:继承接口+默认实现virtual void rotate(int degrees) { // 默认旋转逻辑}// 非虚函数:继承接口+强制实现int id() const { return id_; }
};class Circle : public Shape {
public:void draw() const override; // 必须实现// rotate()可选:使用默认或覆盖// id()不可覆盖,强制使用基类实现
};

🚨 2. 关键陷阱与解决方案

陷阱1:虚函数的不安全默认实现

class Aircraft {
public:virtual void fly(const Airport& dest) {defaultFly(dest); // 默认飞行逻辑}
};class ModelA : public Aircraft {}; // 使用默认flyclass ModelB : public Aircraft {
public:void fly(const Airport& dest) override {customFly(dest); // 自定义飞行}
};// 危险:新增机型忘记覆盖fly
class ModelX : public Aircraft {}; // 意外使用默认飞行逻辑!

解决方案1:分离接口与默认实现

class Aircraft {
public:void fly(const Airport& dest) { // 非虚接口(NVI)doFly(dest); }
private:virtual void doFly(const Airport& dest) = 0; // 强制派生类实现
protected:void defaultFly(const Airport& dest); // 提供默认实现(可选)
};class ModelX : public Aircraft {
private:void doFly(const Airport& dest) override {defaultFly(dest); // 显式选择使用默认}
};

解决方案2:安全提供默认实现

class Aircraft {
public:virtual void fly(const Airport& dest) = 0;
protected:void defaultFly(const Airport& dest); // 保护方法
};// 派生类明确选择
class ModelX : public Aircraft {
public:void fly(const Airport& dest) override {defaultFly(dest); // 安全使用默认}
};

⚖️ 3. 最佳实践指南
设计目标推荐方案示例
强制接口规范✅ 纯虚函数virtual void render() = 0;
安全默认实现🔶 纯虚函数+保护默认方法protected: void defaultRender();
允许覆盖的通用行为⚠️ 虚函数(谨慎使用)virtual void save();
不可更改的通用行为✅ 非虚函数int serialNumber() const;
流程控制固定,步骤可定制✅ 非虚接口模式(NVI)见下文完整示例

现代C++增强

// C++11 override/final
class Widget {
public:virtual void process() final; // 禁止进一步覆盖
};class AdvancedWidget : public Widget {
public:void process() override; // 错误!final函数不可覆盖
};// C++20 contract + virtual
class Database {
public:virtual void commit() [[expects: isConnected()]] [[ensures: dataPersisted()]] = 0;
};

💡 关键设计原则

  1. 非虚接口(NVI)模式

    class GameCharacter {
    public:// 非虚公开接口(控制流程框架)int healthValue() const {// 前置处理(如日志、锁)int val = doHealthValue();// 后置处理(如验证、通知)return postProcess(val);}
    private:// 实现细节委托给派生类virtual int doHealthValue() const {// 默认实现(可选)return calculateBaseHealth();}// 可添加更多扩展点...
    };
    
  2. 纯虚函数的安全默认

    class Printer {
    public:virtual void print(Document& doc) = 0;
    protected:// 安全提供默认实现void defaultPrint(Document& doc) {// 通用打印逻辑}
    };class InkjetPrinter : public Printer {
    public:void print(Document& doc) override {preHeat();defaultPrint(doc); // 显式调用postCool();}
    };
    
  3. 非虚函数的谨慎使用

    class Account {
    public:// 非虚函数:通用且不可更改的行为double balance() const {std::lock_guard<std::mutex> lock(mutex_);return balance_;}
    private:mutable std::mutex mutex_;double balance_;
    };// 所有账户类型使用相同线程安全实现
    class SavingsAccount : public Account {};
    class CheckingAccount : public Account {};
    

危险模式重现

class DataSource {
public:virtual void load() { // 虚函数提供默认实现// 从文件加载的默认逻辑}
};class DatabaseSource : public DataSource {// 忘记覆盖load,错误使用文件加载!
};class NetworkSource : public DataSource {
public:void load() override {// 正确网络加载实现}
};

安全重构方案

class DataSource {
public:void load() {       // 非虚接口doLoad();validateData();}
private:virtual void doLoad() = 0; // 强制派生实现
};class DatabaseSource : public DataSource {
private:void doLoad() override {// 必须实现数据库加载}
};

模板方法模式应用

class ReportGenerator {
public:// 模板方法(非虚)void generateReport() {openDataSource();extractData();      // 虚函数钩子convertFormat();    // 虚函数钩子closeDataSource();}
protected:virtual void extractData() = 0;virtual void convertFormat() {// 默认格式转换}
};class PDFReport : public ReportGenerator {
protected:void extractData() override { /* PDF提取 */ }// 使用默认convertFormat
};
http://www.dtcms.com/a/324892.html

相关文章:

  • 数据结构(17)排序(下)
  • 深度剖析 P vs NP 问题:计算领域的世纪谜题
  • Graham 算法求二维凸包
  • PG靶机 - Resourced
  • 【51单片机按键闪烁流水灯方向】2022-10-26
  • 【LeetCode】102 - 二叉树的层序遍历
  • MVC结构变种——第三章核心视图及控制器的整体逻辑
  • idea中使用maven造成每次都打印日志
  • matlab实现随机森林算法
  • [SUCTF 2019]Pythonginx
  • JS中typeof与instanceof的区别
  • 【精彩回顾·成都】成都 User Group×柴火创客空间:开源硬件驱动 AI 与云的创新实践!
  • JS 注释类型
  • ADK[3]历史对话信息保存机制与构建多轮对话机器人
  • scanpy单细胞转录组python教程(四):单样本数据分析之降维聚类及细胞注释
  • 【Canvas与戳记】黑底金Z字
  • 正确使用SQL Server中的Hint(10)— 常用Hint(2)
  • Spring WebSocket安全认证与权限控制解析
  • 研究揭示 Apple Intelligence 数据处理中可能存在隐私漏洞
  • 【redis初阶】------List 列表类型
  • 通过脚本修改MATLAB的数据字典
  • 【15】OpenCV C++实战篇——fitEllipse椭圆拟合、 Ellipse()画椭圆
  • 【人工智能99问】BERT的原理什么?(23/99)
  • Elasticsearch 保姆级入门篇
  • SpringBoot查询方式全解析
  • 在Mac上搭建本地AI工作流:Dify与DeepSeek的完美结合
  • 数字图像处理2——图像增强
  • AI(1)-神经网络(正向传播与反向传播)
  • 【RL第七篇】PPO训练策略,如何使PPO训练稳定?
  • unity中如何让原人物动画兼容新人物的动画