Effective C++ 学习笔记 条款38 通过复合塑模出has-a或“根据某物实现出”

组合(composition) 是类型之间的一种关系------当一个类型的对象包含另一个类型的对象时,就产生了这种关系。例如:

在这个例子中,Person 对象由 string、Address 和 PhoneNumber 对象组合而成。在程序员中,"组合"这个词有很多同义词,也被称为分层(layering)、包含(containment)、聚合(aggregation)和嵌入(embedding)。

条款32指出,public 继承表示 is-a(是一种)。组合也有它的含义。实际上,组合有两种含义。组合既可以表示 has-a(有一个),也可以表示 is-implemented-in-terms-of(根据某物实现)。这是因为在你的软件中涉及两个不同的领域。程序中的某些对象对应于你正在建模的现实世界中的事物,比如人、车辆、视频帧等,这类对象属于应用域。另一些对象则纯粹是实现产物,比如缓冲区、互斥量、搜索树等,这类对象属于实现域。当组合发生在应用域的对象之间时,它表达的是 has-a 关系;当组合发生在实现域时,它表达的是 is-implemented-in-terms-of 关系。

上面的 Person 类展示的就是 has-a 关系。一个人对象有一个名字、一个地址、一个语音电话号码和一个传真电话号码。你不会说一个人是一个名字,也不会说一个人是一个地址------你会说一个人有一个名字、有一个地址。大多数人很容易区分这其中的差别,所以 is-a 和 has-a 之间的混淆相对较少。

稍微更麻烦一些的是 is-a 与 is-implemented-in-terms-of 之间的区别。比如,假设你需要一个模板来表示"小型对象集合"的类------也就是不带重复元素的集合。既然复用是一件美妙的事情,你的第一反应就是采用标准库中的 set 模板。既然已经有一个现成的模板可用,何必再写一个新的呢?

遗憾的是,set 的实现通常每个元素要承担三个指针的开销。这是因为 set 通常实现为平衡搜索树,以保证对数时间的查找、插入和删除。当速度比空间更重要时,这是一个合理的设计,但你的应用恰恰是空间比速度更重要。于是标准库的 set 对你的场景来说就做了错误的权衡。看来你还是得自己写模板。

不过,复用依然是一件美妙的事。作为精通数据结构的你,知道实现集合有很多选择,其中之一就是用链表。你也知道标准 C++ 库中有 list 模板,所以你决定(复)用它。

特别是,你决定让你那个新生的 Set 模板继承自 list。也就是说,Set<T> 将继承自 list<T>。毕竟在你的实现中,一个 Set 对象实际上就是一个 list 对象。于是你把 Set 模板声明成这样:

此时一切看似都还好,但实际上大有毛病。正如条款32所解释的,如果 D is-a B,那么所有对 B 为真的事情也对 D 为真。然而,一个 list 对象可以包含重复元素,所以如果把值 3051 两次插入一个 list<int>,该 list 将包含两份 3051。相比之下,Set 不允许包含重复元素,所以如果把值 3051 两次插入一个 Set<int>,该集合只会包含该值的一份副本。因此,"Set is-a list"并不成立,因为有些对 list 对象为真的事情,对 Set 对象并不为真。

因为这两个类之间的关系不是 is-a,所以用 public 继承来建模这种关系是错误的。正确的做法是认识到:一个 Set 对象可以根据一个 list 对象来实现:

Set 的成员函数可以大量依赖 list 和标准库其他部分已经提供的功能,因此只要你熟悉 STL 编程的基础知识,实现起来就很简单:

这些函数足够简单,很适合作为内联候选项,不过我明白你在做出明确的内联决定之前,会先回顾条款30中的相关讨论。

可以说,如果 Set 的接口遵循 STL 容器的惯例,它会更符合条款18中"设计易于正确使用、难以错误使用的接口"的建议。但若在此处遵循那些惯例,就不得不给 Set 添加大量内容,反而会模糊它与 list 之间的关系。既然这种关系正是本条款的讨论重点,我们就在教学清晰度上做一点取舍,暂时不追求 STL 兼容性。此外,关于 Set 接口的一些吹毛求疵,也不应掩盖 Set 中一个无可争辩的正确之处:即它与 list 之间的关系------那不是 is-a(尽管乍看之下可能像是),而是 is-implemented-in-terms-of(根据某物实现)。

切记:

1.组合的含义与 public 继承完全不同。

2.在应用域,组合表示 has-a(有一个);在实现域,组合表示 is-implemented-in-terms-of(根据某物实现)。

相关推荐
神仙别闹2 小时前
基于 C++ 实现(控制台)学生成绩管理系统
开发语言·c++
会周易的程序员2 小时前
5 节点边缘冗余方案(上):基于 aiRaft 的物联网高可用控制面设计
c++·分布式·物联网·raft·iot·共识
智者知已应修善业4 小时前
【4060BD_5V应用电路图】2022-12-24
驱动开发·经验分享·笔记·硬件架构·硬件工程
小弥儿4 小时前
GitHub今日热榜 | 2026-09-18:腾讯双项目上榜
学习·开源
qq_199886875 小时前
第6板块·第4节:构建系统与多文件项目
c++·人工智能·gpu算力
知识分享小能手6 小时前
深度学习学习教程,从入门到精通,深度生成模型 —— 知识点详解与代码实现(20)
人工智能·深度学习·学习
汉克老师6 小时前
GESP2026年9月认证C++三级( 第三部分编程题(2、分割字符串))精讲
c++·gesp·小学生·学c++编程
估值探索者7 小时前
【Python量化系统工程化 #08】关了 SSH 就停?systemd 让脚本开机自启 + 异常自动拉起
java·c++·人工智能·分类·数据挖掘
m4Rk_7 小时前
【论文阅读】Agent 记忆机制(74):CompassMem——从相似度检索走向事件图上的记忆导航
论文阅读·人工智能·学习·开源·github
小李不想当小白7 小时前
DMA直接存储器存取(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·分享