跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

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

《Effective C++》条款 34 指出公有继承包含接口继承和实现继承。纯虚函数仅继承接口,强制派生类实现;非纯虚函数提供接口及默认实现;非虚函数则强制继承接口及具体实现。设计时应根据需求选择函数类型,避免将所有函数设为非虚导致僵化,或全设为虚导致职责不清。通过分离接口与默认实现可规避风险,确保编译期发现错误。

赛博行者发布于 2026/3/29更新于 2026/10/2595 浏览
Effective C++ 条款 34:区分接口继承与实现继承

《Effective C++》条款 34:区分接口继承与实现继承

公有继承的本质

公有继承(public inheritance)实质上是两种继承的复合体:函数接口的继承和函数实现的继承。而如何混合搭配这两种继承,就体现在你为基类成员函数所选择的类型上。

三种成员函数的对比

函数类型继承内容设计意图与约束
纯虚函数 (Pure Virtual)仅继承接口强制派生类必须实现该接口。强调"做什么"由基类规定,但"如何做"完全由派生类决定。
虚函数 (Impure Virtual)继承接口和一份缺省实现派生类应该实现该接口,但如果不实现,则使用基类提供的缺省行为。这是灵活性,也潜藏风险。
非虚函数 (Non-Virtual)继承接口和一份强制性实现派生类不应该改变该函数的行为。它代表一种不变性,强调"如何做"是固定的,所有派生类必须遵守。

深入理解三种函数的设计意图

1. 纯虚函数:强制实现契约

纯虚函数通过 = 0 的语法,明确告诉派生类的设计者:"你必须支持这个操作,但我无法提供通用实现"。这最适合定义一种"契约"或"能力"。

代码示例:

class Shape {
public:
    virtual void draw() const = 0; // 纯虚函数:所有图形都必须能被绘制
    virtual ~Shape() = default;
};

Shape::draw() 是纯虚函数,因为画一个圆形和画一个矩形的算法完全不同,基类 Shape 无法给出有意义的默认实现。任何从 Shape 派生的具体类(如 Circle,Rectangle),如果不实现 draw(),自己也会成为抽象类,无法实例化。这就从语法层面保证了接口的一致性。

一个重要的技巧:纯虚函数可以有实现! 你可以为纯虚函数提供定义,但调用它必须使用完全限定名(如 shapePtr->Shape::draw())。这通常用于提供一些公共基础功能,但即便如此,派生类也必须重写该函数。

2. 虚函数:提供缺省实现与风险

虚函数则在强制接口之外,提供了一份"保底"的缺省实现。它说:"你最好自定义这个行为,但如果你不自定义,可以用我这个默认的"。

代码示例:

// 初始设计:存在风险
class Airplane {
public:
    {
        
    }
};
  :  Airplane { ... };

  :  Airplane { ... };

virtual void fly(const Airport& destination)
// 默认的飞行逻辑...
class
ModelA
public
// 使用默认 fly
class
ModelB
public
// 使用默认 fly

这个设计的风险在于,如果新加入的 ModelC 有特殊的飞行方式,但程序员忘记重写 fly 函数,那么 ModelC 的飞机就会错误地使用默认飞行模式,可能导致严重问题。

更安全的设计:接口与默认实现分离

为了解决这个问题,条款推荐了两种方法:

将默认实现分离为独立的保护函数:

class Airplane {
public:
    virtual void fly(const Airport& destination) = 0; // 纯虚接口
protected:
    void defaultFly(const Airport& destination); // 独立的默认实现
};
class ModelA : public Airplane {
public:
    virtual void fly(const Airport& destination) override {
        defaultFly(destination); // 显式申请使用默认实现
    }
};

利用纯虚函数本身提供默认实现:

class Airplane {
public:
    virtual void fly(const Airport& destination) = 0;
};
// 为纯虚函数提供实现
void Airplane::fly(const Airport& destination) {
    /* 默认实现 */
}
class ModelA : public Airplane {
public:
    virtual void fly(const Airport& destination) override {
        Airplane::fly(destination); // 显式调用基类的纯虚函数实现
    }
};

这两种方法的核心思想都是:切断"继承接口"和"继承缺省实现"的自动关联,要求派生类显式地表明意图。这样,如果 ModelC 不重写 fly,就会因为基类是抽象类而无法编译,错误在编译期就被发现。

3. 非虚函数:强制不变性

非虚函数代表的意义是"不变性凌驾于特异性之上"。它表示这个行为是基类认定的、所有派生类都不应该改变的核心特性。

代码示例:

class Shape {
public:
    int objectID() const { ... } // 非虚函数
};

对于 objectID() 这样的函数,其计算对象 ID 的方式应该是全局统一的,不应该因为对象是矩形或圆形而改变。如果派生类重定义非虚函数(这本身是允许的,但会遮蔽基类函数),通常意味着设计出了问题,应该重新审视这个函数是否真的应该是非虚函数。

常见设计错误与准则

理解了这三种函数的区别,就能避免两个常见的设计错误:

  1. 将所有函数声明为非虚函数:这会让派生类没有特化的空间,僵化了设计。尤其需要注意的是,如果类要做基类,析构函数必须是虚函数。
  2. 将所有函数声明为虚函数:这暗示类设计中缺乏不变性,所有行为都允许被改变,可能意味着职责划分不清。同时,对于完全不希望被重写的函数,应该用 non-virtual 明确表达设计意图。

关于虚函数的性能,有一个 80-20 法则:一个程序 80% 的时间往往运行在 20% 的代码上。因此,只要将虚函数用在真正需要多态的地方,它们带来的运行时开销通常是可接受的。

总结

记住条款 34 的精髓在于培养一种精确的设计意识:

  • 使用纯虚函数 来定义强制性的接口契约。
  • 使用虚函数 来提供可选的缺省实现,但最好通过接口与实现分离的技术来避免潜在风险。
  • 使用非虚函数 来规定强制性的不变实现。

目录

  1. 《Effective C++》条款 34:区分接口继承与实现继承
  2. 公有继承的本质
  3. 三种成员函数的对比
  4. 深入理解三种函数的设计意图
  5. 1. 纯虚函数:强制实现契约
  6. 2. 虚函数:提供缺省实现与风险
  7. 3. 非虚函数:强制不变性
  8. 常见设计错误与准则
  9. 总结

更多推荐文章

查看全部
  • AI 产品架构设计:从 0 到 1 搭建信息架构与核心业务流程
  • CCF 大模型论坛北京会议:大模型技术进展与挑战探讨
  • Python 字符串格式化、编码与深浅拷贝详解
  • 18 年政务系统实战:Java 自研框架架构决策复盘
  • Flutter 三方库 shelf_modular 的鸿蒙化适配指南
  • C++26 核心功能详解:特性、语法与示例代码
  • 蓝桥杯C/C++大学B组省赛真题解析与实战技巧
  • C++26 契约编程新特性:利用静态与动态检查提升代码健壮性
  • VSCode 新版禁用 Ctrl+I 快捷键唤起 Copilot AI 对话框
  • C++ 关联式容器核心解析:map 与 set 实战指南
  • OpenClaw 多机器人飞书群聊配置实战
  • stl-thumb:3D模型文件预览工具
  • FPGA 是什么:从原理到应用场景的通俗解读
  • AI 办公实战指南:7 套书籍助你精准提效与职场进阶
  • RabbitMQ 深度详解
  • Docker CE 在 Kali/Ubuntu 系统上的安装与配置指南
  • OpenClaw 跨平台部署与 SearXNG 免费搜索配置教程
  • AI 驱动游戏:鸿蒙生态的机会在哪里?
  • LLaMA-Factory 命令行工具 llamafactory-cli 使用指南
  • 渐进式 AIGC 系统:支持 Nano-Banana 绘画、VEO3/Sora-2 视频及 Agent 智能体

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online