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

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.xdnf.cn/news/17647.html

相关文章:

  • 数据库面试题集
  • DFT的几点理解(二)
  • 计算二分类误差时的常见错误及解决方案
  • 农经权二轮延包—已有软件与后续研究
  • Spring之【详解AOP】
  • NLP 2025全景指南:从分词到128专家MoE模型,手撕BERT情感分析实战(第四章)
  • scanpy单细胞转录组python教程(三):单样本数据分析之数据标准化、特征选择、细胞周期计算、回归等
  • 制动电阻烧损记录学习
  • Spark执行计划与UI分析
  • JVM调优好用的内存分析工具!
  • jvm有哪些垃圾回收器,实际中如何选择?
  • 工业相机选择规则
  • leetcode经典题目——单调栈
  • 机器学习第八课之K-means聚类算法
  • Android 16 KB页面大小适配的权威技术方案总结
  • Android Camera 打开和拍照APK源码
  • Suno API V5 全面升级——多语言接入,开启 AI 音乐创作新时代
  • GPT‑5 重磅发布
  • 【开源】分层状态机(HFSM)解析:复杂逻辑的清晰表达与FPGA实现(附完整的Verilog交通灯案例及仿真)
  • Loki+Alloy+Grafana构建轻量级的日志分析系统
  • 随机向量正交投影定理(Orthogonal Projection Theorem, OPT)_学习笔记
  • 【YOLO学习笔记】YOLOv11详解
  • Vue 3 快速入门 第五章
  • 强制类型转换
  • 五种 IO 模型与阻塞 IO
  • vscode uv 发布一个python包:编辑、调试与相对路径导包
  • 【代码随想录day 16】 力扣 112. 路径总和
  • printf函数格式化输出攻略
  • SQL(结构化查询语言)的四大核心分类
  • 【Jenkins入门以及安装】