Effective C++ 学习笔记 条款39 明智而审慎地使用private继承
条款32 展示了 C 将 public 继承视为 is-a 关系。它通过这样一个例子来说明当 Student 以 public 方式继承自 Person 时如果函数调用需要编译器会隐式地将 Student 转换为 Person。这里值得用 private 继承代替 public 继承再把那部分例子重复一遍显然private 继承并不意味着 is-a。那它意味着什么呢“等等”你说“在探讨含义之前先说说行为吧。private 继承的行为是怎样的”好的关于 private 继承的第一条规则你刚才已经见识过了与 public 继承相反如果类之间的继承关系是 private 的编译器通常不会将派生类对象如 Student转换为基类对象如 Person。这就是为什么对 s 调用 eat 会失败。第二条规则是从 private 基类继承来的成员在派生类中都会变成 private 成员即使它们在基类中是 protected 或 public 的。行为说完了。接下来就是含义。private 继承意味着 is-implemented-in-terms-of根据某物实现。如果你让类 D 私有继承自类 B你这样做是因为你感兴趣的是利用类 B 中已有的某些特性而不是因为 B 类型和 D 类型对象之间存在任何概念上的关系。因此private 继承纯粹是一种实现技术这就是为什么你从 private 基类继承的所有东西在类中都变成 private 的原因——这些都只是实现细节。使用条款34中引入的术语来说private 继承意味着只应继承实现接口应被忽略。如果 D 私有继承自 B意味着 D 对象是根据 B 对象来实现的仅此而已。private 继承在软件设计层面毫无意义只存在于软件实现层面。private 继承意味着 is-implemented-in-terms-of这个事实有点令人不安因为条款38指出组合也可以表达同样的意思。那该如何在它们之间做出选择呢答案很简单能用组合就用组合只有在必须的时候才用 private 继承。什么时候必须主要是在涉及 protected 成员和/或虚函数的时候不过还有一种边缘情况——空间方面的考量可能会让天平倾向 private 继承。边缘情况我们稍后再谈。毕竟它只是边缘情况。假设我们正在开发一个涉及 Widget 的应用程序并且决定需要更好地了解 Widget 的使用情况。例如我们不仅想知道 Widget 成员函数被调用的频率还想知道调用比率随时间如何变化。执行阶段不同的程序在不同阶段会有不同的行为特征。例如编译器在解析阶段使用的函数与优化和代码生成阶段使用的函数就有很大不同。我们决定修改 Widget 类让它能记录每个成员函数被调用的次数。在运行时我们会定期检查这些信息可能还会结合每个 Widget 的值以及我们认为有用的其他数据。为了实现这个功能我们需要某种定时器以便知道何时该收集使用统计数据。与其从头编写新代码我们更倾向于复用现有代码。于是我们在自己的工具包中翻找了一番欣喜地发现了下面这个类这正好是我们需要的。Timer 对象可以按我们需要的频率来滴答计时每次滴答时它都会调用一个虚函数。我们可以重新定义那个虚函数让它检查当前 Widget 世界的状态。完美为了让 Widget 能够重新定义 Timer 中的虚函数Widget 必须继承自 Timer。但在这种情况下public 继承并不合适。并不是说一个 Widget 是一个 Timer。Widget 的客户不应该能在 Widget 上调用 onTick因为这并不是 Widget 概念接口的一部分。允许这样的函数调用会让客户很容易错误地使用 Widget 接口这明显违反了条款18中“设计易于正确使用、难以错误使用的接口”的建议。因此public 继承在这里不是一个可行的选项。于是我们采用 private 继承通过 private 继承Timer 中的公有 onTick 函数在 Widget 中变成了私有成员而我们在重新声明它时也将其保持在私有区域。再次强调把 onTick 放在公有接口中会误导客户让他们以为自己可以调用它那就会违反条款18。这是一个不错的设计但值得指出的是private 继承并不是严格必要的。如果我们执意要用组合来代替也是可行的。我们只需在 Widget 内部声明一个私有的嵌套类让它公有继承 Timer在那里重新定义 onTick然后在 Widget 中放置一个该类型的对象即可。下面是这种方式的粗略框架这种设计比单纯使用 private 继承要复杂因为它同时涉及public继承和组合还引入了一个新类WidgetTimer。坦白说我展示这个例子主要是为了提醒你设计问题的解决方式往往不止一种训练自己考虑多种方案是很有价值的另见条款35。尽管如此我能想到两个理由让你可能更倾向于采用“public 继承加组合”的方式而非 private 继承。第一你可能想让 Widget 允许有派生类但同时又想阻止派生类重新定义 onTick。如果 Widget 直接继承自 Timer这一点就做不到——即使采用 private 继承也不行回想条款35派生类可以重新定义虚函数即使它们不被允许调用这些函数。但如果 WidgetTimer 是 Widget 中的私有成员且它继承自 Timer那么 Widget 的派生类就无法访问 WidgetTimer也就不能继承自它或重新定义它的虚函数。如果你用过 Java 或 C#并且怀念那种能够阻止派生类重新定义虚函数的能力即 Java 的 final 方法和 C# 的 sealed 方法那么现在你就有思路在 C 中近似实现那种行为了。第二你可能想最小化 Widget 的编译依赖。如果 Widget 继承自 Timer那么在编译 Widget 时 Timer 的定义必须可见所以定义 Widget 的文件可能必须 #include Timer.h。另一方面如果把 WidgetTimer 移出 Widget且 Widget 只包含一个指向 WidgetTimer 的指针那么 Widget 只需一个 WidgetTimer 类的前置声明就能应付过去无需 #include 任何与 Timer 相关的东西。对于大型系统这种解耦可能非常重要关于最小化编译依赖的细节请参见条款31。我之前说过private 继承主要在有派生类想要访问基类的 protected 成员或者想要重新定义基类的一个或多个虚函数时才有用但类之间的概念关系是 is-implemented-in-terms-of 而非 is-a。不过我也提到过还有涉及空间优化的边缘情况可能会让你倾向于 private 继承而非组合。这个边缘情况确实比较“边缘”它仅在你处理一个没有数据成员的类时才适用。这样的类没有非静态数据成员没有虚函数因为虚函数的存在会给每个对象增加一个虚表指针——参见条款7也没有虚基类因为虚基类也会带来大小开销——参见条款40。从概念上讲这种空类的对象应该不占用空间因为没有任何需要存储的、与对象相关的数据。然而由于技术上的原因C 规定独立对象freestanding objects的大小必须非零所以如果你这样做你会发现sizeof(HoldsAnInt) sizeof(int);一个 Empty 数据成员确实需要内存。在大多数编译器下sizeof(Empty) 是 1因为 C 对于零大小独立对象的禁令通常是通过在“空”对象中静默插入一个 char 来满足的。然而对齐要求参见条款50可能会让编译器给 HoldsAnInt 这样的类添加填充字节所以 HoldsAnInt 对象很可能不仅仅增加一个 char 的大小而是实际上扩大到足以容纳第二个 int在我测试过的所有编译器上情况确实如此。不过你可能已经注意到了我一直谨慎地说“独立”对象的大小不能为零。这个限制并不适用于派生类对象中的基类子对象因为它们不是独立的。如果你让 HoldsAnInt 继承自 Empty而不是包含一个该类型的对象你几乎可以肯定 sizeof(HoldsAnInt) sizeof(int)。这就是所谓的空基类优化empty base optimizationEBO在我测试过的所有编译器上都有实现。如果你是库开发者且客户关心空间EBO 值得了解。同样值得注意的是EBO 通常仅在单继承下可行因为 C 对象布局规则意味着 EBO 通常不能应用于有多个基类的派生类。在实际中“空”类并不真正是空的。虽然它们从不拥有非静态数据成员但它们通常包含 typedef、枚举、静态数据成员或非虚函数。STL 中有许多技术上为空的类却包含有用的成员通常是 typedef其中包括 unary_function 和 binary_function 这些基类——用户自定义的函数对象类通常从它们继承。得益于 EBO 的广泛实现这种继承很少会增加继承类的大小。不过还是让我们回到基本面上。大多数类都不是空的所以 EBO 很少能成为采用 private 继承的合理理由。而且大多数继承对应的是 is-a 关系那是 public 继承的职责而非 private 继承。组合和 private 继承都表示 is-implemented-in-terms-of但组合更容易理解所以只要能用组合你就应该用。private 继承最可能成为合理设计策略的场景是处理两个没有 is-a 关系的类且其中一个需要访问另一个的 protected 成员或者需要重新定义另一个的一个或多个虚函数。即便在这种情况下我们也看到 public 继承加包含containment的组合往往能实现你想要的行为尽管设计复杂度会更高。谨慎地使用 private 继承意味着在考虑过所有替代方案之后它仍然是在你的软件中表达两个类之间关系的最佳方式时才使用它。切记1.private 继承表示 is-implemented-in-terms-of。它通常不如组合但在派生类需要访问基类 protected 成员或需要重新定义继承来的虚函数时它是有意义的。2.与组合不同private 继承可以启用空基类优化。这对致力于最小化对象大小的库开发者来说可能很重要。

相关新闻

基于IEEE1588-PTP的高精度时间同步系统:Python与C混合编程实践

基于IEEE1588-PTP的高精度时间同步系统:Python与C混合编程实践

1. 项目概述与核心价值最近在做一个分布式数据采集的项目,遇到了一个挺头疼的问题:分布在几个机柜里的十几台设备,各自采集到的数据时间戳对不上,误差能到几十毫秒。这对于后续的数据融合和分析来说简直是灾难。为了解决这个问题&…

2026/10/1 4:07:11 阅读更多 →
HarmonyOS应用开发实战:小事记 - UIAbility 的冷启动/热启动/后台启动三种场景与 launchParam 解析

HarmonyOS应用开发实战:小事记 - UIAbility 的冷启动/热启动/后台启动三种场景与 launchParam 解析

前言 在 HarmonyOS 的 Stage 模型中,UIAbility 的启动方式决定了其生命周期回调的顺序和参数。理解冷启动(Cold Start)、热启动(Hot Start)和后台启动(Background Launch)三种场景的区别&#…

2026/10/11 3:02:09 阅读更多 →
从零实现C++ Json-RPC框架:深入理解网络编程与协议设计

从零实现C++ Json-RPC框架:深入理解网络编程与协议设计

1. 项目概述:为什么我们需要自己造一个Json-Rpc轮子?在C的后端服务开发里,远程过程调用(RPC)是个绕不开的话题。你可能用过gRPC,或者Thrift,它们功能强大,生态成熟。但有时候&#x…

2026/10/5 21:13:38 阅读更多 →

最新新闻

2026年AI大模型API平台选型指南:五家服务商四维评测与企业参考

2026年AI大模型API平台选型指南:五家服务商四维评测与企业参考

API聚合平台已经从简单的转发接口,演进为具备协议适配、智能路由、审计计费、多成员协作与容灾调度能力的数字基础设施——一次服务中断可能让生产流水线停摆,一笔模糊账单会埋下财务审计隐患。本文基于实测数据,从系统稳定性、协议标准化、企…

2026/10/12 3:42:12 阅读更多 →
每秒300笔误解背后:高频交易与量化监管的本质解析

每秒300笔误解背后:高频交易与量化监管的本质解析

上周和一个做主观多头的老哥吃饭,他刷到一条讲“每秒300笔”的短视频,扭头问我:“你们做量化的,真能一秒钟开三百枪?”我愣了一下,因为这个问题本身就暴露了大众对高频交易的刻板印象。后来我发现&#xff…

2026/10/12 3:42:12 阅读更多 →
多智能体系统的总调度器:职责、实现与落地指南

多智能体系统的总调度器:职责、实现与落地指南

先交代一句我自己的背景心态:我做过不少包含多个算法模块的自动化系统,最开始大家都很单纯,觉得只要把几个专长不同的模型拼在一个流程里,任务就能自动完成。结果真上了生产环境,第一个崩溃的不是单个节点,…

2026/10/12 3:42:12 阅读更多 →
2026年国内大模型API聚合服务解析:词元之河的核心优势与五个选型指标

2026年国内大模型API聚合服务解析:词元之河的核心优势与五个选型指标

国内开发者调用海外模型有三大阻碍:网络不稳定、支付渠道受限、成本偏高。调研显示超过八成的国内开发者需要API聚合方案支撑日常开发,直连官方接口每月损耗的有效请求可达一成半,换到靠谱聚合平台后能降到千分之一以下。本文以词元之河(Toke…

2026/10/12 3:42:12 阅读更多 →
基于Qt5.8的手写数字识别界面:画布格式对齐与kNN模型实践

基于Qt5.8的手写数字识别界面:画布格式对齐与kNN模型实践

简介:这是一份基于Qt5.8开发的手写数字识别桌面应用完整工程,面向Qt初学者、计算机视觉爱好者及课程设计开发者,以写字板形式让用户用鼠标绘制数字,并调用SVM模型完成识别,直观演示了GUI与机器学习结合的实现路径。压缩…

2026/10/12 3:42:12 阅读更多 →
scope 项目中 klog 日志库的按需发布流程(RELEASE.md)全解析

scope 项目中 klog 日志库的按需发布流程(RELEASE.md)全解析

云原生可观测性容器编排运维 【免费下载链接】scope Monitoring, visualisation & management for Docker & Kubernetes 项目地址: https://gitcode.com/gh_mirrors/sc/scope 点击查看 免费下载 导读 klog 是 Kubernetes 生态广泛使用的 Go 分级日志库&am…

2026/10/12 3:41:11 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →