[翻译] Effective C++, 3rd Edition, Item 32: 确保 public inheritance 模拟 "is-a"(上)

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

Item 32: 确保 public inheritance 模拟 "is-a"

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.csdn.net/fatalerror99/

Some Must Watch While Some Must Sleep (W. H. Freeman and Company, 1974) 这本书中,William Dement 讲述了一个他试图让他的学生的记住他的课程中最重要的东西的故事。书中声称,他告诉他的班级,一般的英国中小学生对于 1066 年发生的 Hastings 战争的历史并没有什么了解。他着重强调,如果一个孩子记住了一点儿什么的,他或者她也就是记住了 1066 这个年代。对于上他的课程的学生,Dement 滔滔不绝地讲,其中只有很少的重要信息,包括安眠药却引起了失眠这样充满趣味的事情。他希望他的学生即使忘记课程中讨论的其它每一件事,也能记住这些很少的重大事件,而且他在整个学期再三地回顾这些基础的内容。

在课程结束的时候,期末考试的最后一道题是:“写下从这个课程中得到的,你一生都将确切地记住的一件事。”当 Dement 给这次考试打分的时候,他几乎晕了过去。几乎每一个人都写了"1066"。

因此,我一再煞费苦心地向你宣扬,使用 C++ 语言进行 object-oriented programming 时唯一最重要规则就是:public inheritance(公开继承)意味着 "is-a"。要让这个规则刻骨铭心。

如果你写了一个 class D ("Derived") 从 class B ("Base") 公开继承,你就是在告诉 C++ 编译器(以及你的代码的读者)每一个类型为 D 的对象也是一个类型为 B 的对象,但是反之则不然。你就是在说 B 描绘了一个比 D 更一般的概念,D 描述了一个比 B 更特殊的概念。你就是在声称一个类型为 B 的对象可以使用的任何地方,一个类型为 D 的对象一样可以使用,因为每一个类型为 D 的对象也就是一个类型为 B 的对象。另一方面,如果你需要一个类型为 D 的对象,一个类型为 B 的对象则不行:每一个 D 都是一个 B,但是反之则不然。

C++ 坚持对 public inheritance 的这一解释。考虑这个例子:

class Person {...};

class Student: public Person {...};

我们从日常的经验知道每一个学生都是一个人,但并不是每一个人都是一个学生。这就是由这个继承体系严格确定的意义。我们期望每一件对于人来说成立的事情——例如,他或她有一个出生日——对于一个学生来说也成立。我们不期望每一件对于学生来说成立的事情——例如,他或她在一所特定的学校注册——对于普通人来说也成立。一个人的概念比一个学生的概念更普通,一个学生一个专门类型的人。

在 C++ 领域中,任何期望引数类型为 Person(或 pointer-to-Person 或 reference-to-Person)的函数都可以接受一个 Student object(或 pointer-to-Student 或 reference-to-Student):

void eat(const Person& p);            // anyone can eat

void study(const Student& s);         // only students study

Person p;                             // p is a Person
Student s;                            // s is a Student

eat(p);                               // fine, p is a Person

eat(s);                               // fine, s is a Student,
                                      // and a Student is-a Person

study(s);                             // fine

study(p);                             // error! p isn't a Student

这一点只对 public inheritance 才成立。只有 Student 以 public 方式从 Person 派生,C++ 才有我所描述的行为。private inheritance 意味着完全不同的其它事情(参见 Item 39),而 protected inheritance 究竟意味什么使我困惑至今。

public inheritance 和 is-a 等价听起来简单,但有时你的直觉会误导你。例如,企鹅是一种鸟没有问题,而鸟能飞也没有问题。如果我们天真地试图用 C++ 来表达,我们就会得到:

class Bird {
public:
  virtual void fly();                  // birds can fly

  ...
};

class Penguin:public Bird {            // penguins are birds
  ...
};

突然间我们遇到了麻烦,因为这个继承体系表示企鹅能飞,我们知道这不是真的。发生了什么呢?

在这种情况下,我们成了不严谨的语言——英语的牺牲品。当我们说鸟能飞的时候,我们的意思并非是说所有种类的鸟都能飞,我们不过是说,大体上,鸟有飞的能力。如果我们说得更准确些,我们应该承认有几种不能飞的鸟,并提出如下继承体系,它对事实的模拟要好得多:

class Bird {
  ...                                       // no fly function is declared
};

class FlyingBird: public Bird {
public:
  virtual void fly();
  ...
};

class Penguin: public Bird {

  ...                                       // no fly function is declared

};

这个继承体系比最初的设计更忠实于我们真正知道的东西。

至此我们还是没有完全做好关于这些鸟的事情,因为对于某些软件系统来说,可能并不需要区分能飞的和不能飞的鸟。如果你的应用程序对于鸟喙和鸟翼做了很多处理,而不打算对飞行做什么处理的话,最初的 two-class 的继承体系可能完全适用。它是对“没有一个适用于所有软件的完美设计”这样的事实的一个简单反映。最好的设计依赖于系统究竟期望做什么,无论现在还是未来。如果你的程序对飞行一无所知,而且也不期望以后能知道些什么,那么不分辨能飞与不能飞的鸟可能就是一个非常完美的设计决策。事实上,它可能比区分它们的设计更为可取,因为你试图模拟的世界中就没有这样一种区分

对于如何处理我所说的“所有的鸟能飞,企鹅是鸟,企鹅不能飞,啊……哦……”的问题,还有另一种思想观念。那就是为企鹅重定义 fly 函数,以便让它产生一个运行时错误。

void error(const std::string& msg);       // defined elsewhere

class Penguin: public Bird {
public:
  virtual void fly() { error("Attempt to make a penguin fly!");}

  ...

};

认可“这里所说的一些事情与你所想的可能不同”是很重要的。这不是说“企鹅不能飞”。而是说“企鹅能飞,但对它试图真的这样做就是一个错误”。

(点击此处,接下篇)

从跳表无锁插入到元数据原子发布:现代 C++ 存储内核如何跨越物理 I/O 与 CPU 内存模型 在现代工业级 LSM-Tree 存储引擎的内核设计中,C++ 工程师对“无锁化(Lock-Free)”与“纳秒级确定性”的极致偏执,几乎被雕刻进了每一行代码里。为了抹平动态内存分配器的碎片与互斥锁竞争,我们在写入路径上采用紧凑的 Arena 内存池与无锁单写多读跳表(),将插入操作的延迟死死压制在 CPU 缓存流水线的物理极限;为了让前台点查(Point Get)彻底告别昂贵的全局锁争用与特权级上下文切换,引擎巧妙融合了线程局部缓存( 阅读详情

相关推荐

effective c++ 条款35: 使公有继承体现 "是一个" 的含义

<br />条款35: 使公有继承体现 "是一个" 的含义 <br /> 条款35: 使公有继承体现 "是一个" 的含义<br /> <br />在"Some Must Watch While Some Must Sleep"( W. H. Freeman and Company, 1974)一书中,William Dement讲了一个故事,故事说的是他如何让学生们记住他的课程中最重要的部分。"据说,",他告诉他的学生,"一般的英国学生除了记得Hastings战役发生在1066年外,再也不记得

fengxinziyang的专栏 539

[翻译] Effective C++, 3rd Edition, Item 32: 确保 public inheritance 模拟 "is-a"(下)

(点击此处,接上篇)你能说出其中的区别吗?从错误被发觉时间方面看。“企鹅不能飞”的禁令可以由编译器强令执行,但是对“让企鹅真的去飞是一个错误”的规约的违反,只有在运行时才能被发觉。为了表达“企鹅不能飞”这个限制,你要确保不要为 Penguin 对象定义这样的函数:class Bird {  ...                                // no fly

艺术编程 2267

Effective C++Item 32: 确保 public inheritance 模拟 "is-a"

在 Some Must Watch While Some Must Sleep (W. H. Freeman and Company, 1974) 这本书中,William Dement 讲述了一个他试图让他的学生的记住他的课程中最重要的东西的故事。书中声称,他告诉他的班级,一般的英国中小学生对于 1066 年发生的 Hastings 战争的历史并没有什么了解。他着重强调,如果一个孩子记住了一点...

janeqi1987的专栏 279

[翻译] Effective C++, 3rd Edition, Item 38: 通过 composition(复合)模拟 "has-a"(有一个)或 "is-implemented-in-terms-of"(是根据……实现的)

Item 38: 通过 composition(复合)模拟 "has-a"(有一个)或 "is-implemented-in-terms-of"(是根据……实现的)作者:Scott Meyers译者:fatalerror99 (iTePubs Nirvana)发布:http://blog.csdn.net/fatalerror99/composition(复合)是在 object

艺术编程 4682

[翻译] Effective C++, 3rd Edition, Item 34: 区分 inheritance of interface(接口继承)和 inheritance of implementation(实现继承)(上)

Item 34: 区分 inheritance of interface(接口继承)和 inheritance of implementation(实现继承)作者:Scott Meyers译者:fatalerror99 (iTePubs Nirvana)发布:http://blog.csdn.net/fatalerror99/(public) inheritance 这个表面上简

艺术编程 4160

C++——C++基础】

本文系统介绍了C++中的命名空间、输入输出、缺省参数、函数重载与引用机制。命名空间有效避免标识符冲突,通过namespace和::实现作用域隔离;C++输入输出流(cin/cout)基于std命名空间,支持自动类型识别;缺省参数需从右向左连续定义,且声明中指定默认值;函数重载通过形参差异实现多态;引用作为变量别名,无额外内存开销,广泛用于传参与返回值,提升效率,但不可改变指向,且不能返回局部变量引用。

SHARK_pssm的博客 178

顺序表(Sequential List)详解:从数组到 C 语言实现

顺序表是用连续存储单元依次存储数据元素的线性结构,数组则是其最常见的实现方式,两者本质相同、仅视角不同——数组关注物理存储,顺序表关注逻辑关系。顺序表支持随机访问 $O(1)$,但插入删除需移动元素,效率为 $O(n)$。本文用 C 语言静态数组完整实现了顺序表的初始化、增删改查操作,帮助初学者在不涉及指针的前提下理解其核心原理,为后续学习动态顺序表打下基础。

Guangyu536的博客 307

【C语言】初识 C 语言(点到为止)

C 语言概览

BaconZzz的博客 191

Qwen3-ForcedAligner-0.6B 私有化部署实战:从模型离线、Docker封装到会议原声精准回听

text=text,模型离线下载版本固定SHA256目录规划环境隔离Docker封装GPU绑定健康检查长音频切片全局时间恢复临时文件清理日志脱敏离线验证模型升级快速回滚这些内容看起来和“模型算法”关系不大,却决定了一个 AI 模块究竟只能停留在 Demo,还是能长期运行在真实系统里。已经知道说了什么↓准确找到它什么时候说逐字稿点击回听搜索定位纪要复核会议资料追溯从产品界面上看,它可能只是用户点击一句话以后,播放器准确跳到了对应位置。

shxjnpl的博客 182

C++】 定时器进阶:多层时间轮原理与任务迁移

单层时间轮虽然能够以较低的开销管理大量定时任务,但当任务延迟时间跨度很大时,要么需要大量槽位,要么需要额外维护轮数。多层时间轮通过将不同时间范围的任务放到不同层级中,实现类似“秒针、分针、时针”的分级管理。本文主要介绍多层时间轮为什么出现、各层如何划分、定时任务如何选择层级,以及高层任务如何逐步迁移到低层并最终执行。

qq_64148519的博客 115

C++ STL set 与 map 全套详解:关联式容器、红黑树底层、代码坑点、刷题实战

本文系统讲解STL中set/multiset/map/multimap的接口、底层红黑树原理及高频坑点,对比有序与无序容器性能差异,结合力扣例题剖析实际应用,涵盖迭代器特性、插入删除机制、区间操作与面试重点,全面掌握关联式容器核心知识。

xxwxx__的博客 213

Linux驱动子系统:Clock(CCF)子系统 —— Consumer和Provider(004)

Linux驱动子系统:Clock Common Framework,通用时钟框架

weixin_43390831的博客 282

​​​​​​​第 4 节 如何用 C++ 设计一个 AI 推理 SDK 的核心接口

AI推理SDK核心接口应遵循“环境→模型→执行器→张量”四层抽象,采用不透明指针保证ABI稳定,以C API为底座、C++ API为封装,统一错误处理,并权衡张量、内存、异步、线程安全与插件扩展。参考ONNXRuntime、TensorRT、OpenVINO设计,实现易用、高性能、可扩展的工业级接口。

Question_123456的博客 521

附录 C:AOSP 不在公共框架 API 中采用 Kotlin 的原因

以及源码。契约生命周期约束设备发布后会锁定某个 SDK 版本长达接近十年。每一条签名,必须兼容未来所有 kotlinc 版本、Kotlin 元数据格式变更、标准库改动。内部 Kotlin 不受该限制:编译器签名变化可以通过下一次全量重编译抹平。二进制 ABI 映射差异Java 源码声明与 JVM 签名一一对应。Kotlin 一条源码会产出一组 JVM 产物:重载方法、名字混淆、伴生对象访问器、合成辅助函数、Continuation 参数、元数据块;组合结果完全依赖编译器

dengfuma的专栏 741

STM32 USART 详解(八):C I/O 串口重定向原理与实现

本文深入解析STM32串口重定向C标准I/O的核心原理与实现。文章从printf/scanf底层机制入手,剖析数据流向,并提供fputc与fgetc的完整重定向代码。同时深度解析换行符及输入机制问题,最后通过完整测试验证数据流~

2502_91499791的博客 137

DeepSeek Harness 系列(02):万物皆插件——Cordis 核心设计深度解读

dsh 的所有能力都建立在 Cordis 插件框架之上。这篇文章结合真实源码,深度解读 Cordis 五个核心机制:Plugin、Context、Service、Event、Effect——每个机制讲清楚「是什么、怎么用、为什么这么设计」。读完之后,dsh 的一切都会豁然开朗。

Cheson的专栏 527

9.13华为OD机试真题 新系统 - 路由器网络连通性检测 (Java/Py/C/C++/Js/Go)

华为OD机试真题 新系统【路由器网络连通性检测】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD机试真题 【路由器网络连通性检测】多语言题解。

qq_45776114的博客 390

实现pytorch resnet18功能(二,c++ cudnn)

/输出32*32*32-----------------------显然输入也是32*32*32。, _ci(ci), _co(co), _h(h), _w(w), _jjh(juanjihe), _pad(pading) {//int stride=2,juanjihe=1时,pading=0;原因是残差块中最后一步只实现了相加,并没有实现relu,所以这个架构中,所有残差块类都跟有relu!

ganggangwawa的博客 203

Type-C快充取电芯片工作流程:从适配器识别到电压输出

PW6607是一颗USB电源传输接收SINK端控制器芯片,PW6607可以从PD/QC适配器电源请求PW6606设定的电压。当连接USB电源时,PD/QC协议自动通讯,完成PW6607设定的电压请求通讯,并输出通讯设定的电压。PW6606内置 PD 通讯模块和QC通讯,集成度高,外围简洁。PD协议优先级高于QC协议,仅当PD诱骗异常或者前端不支持PD时,才会进行 QC诱骗。PD 握手会优先寻找适配设置电压的 PDO 挡位,当没有需要的挡位时,自动降低电压挡位诱骗。5V,9V,12V和20V(PD);

MILLET6的博客 141
上一篇: [翻译] Effective C++, 3rd Edition, Chapter 6. Inheritance(继承)和 Object-Oriented Design(面向对象设计)
下一篇: [转载] 行走论坛,此帖独尊
fatalerror99
博客等级 码龄24年 166粉丝 20原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值