[Effective C++第3版][第33条] 防止隐藏继承的名字

C++ 知识补充 向量(vector)是一个封装了动态大小数组的顺序容器(SequenceContainer)。跟任意其它类型容器一样,它能够存放各种类型的对象。可以简单的认为,向量是一个能够存放任意类型的动态数组。C++中map提供的是一种键值对容器,里面的数据都是成对出现的,如下图:每一对中的第一个值称之为关键字(key),每个关键字只能在map中出现一次;第二个称之为该关键字的对应值。 阅读详情

第33条:     防止隐藏继承的名字

莎士比亚对于“名字”有着独特的见解。“名字意味着什么?玫瑰不叫玫瑰,依然芬芳如故。”大师还写道:“倘若有人偷窃了我的好名声……事实上会让我变得一贫如洗。”让这两段至理名言引领我们去探究 C++ 中继承的名字。

事实上,本节讨论的问题与继承并没有太大关系。它仅仅关系到作用域。我们都能读懂下面的代码:

int x;                                 // 全局变量

 

void someFunc()

{

  double x;                            // 局部变量

 

  std::cin >> x;                       // 读一个新值赋给局部变量 x

}

为 x 赋值的语句是关于局部变量 x 的,而不是全局变量 x ,这是因为内部作用域隐藏了(“遮挡了”)外部作用域的名字。我们可以将这种域间状况用下图描述:

3301.jpg

当编译器执行至 someFunc 的作用域内并且遇到名字 x 时,它将在局部作用域内查找,以便确认此处是否包含与 x 这个名字相关的操作。因为如果有的话,编译器就不会再去检查其它任何作用域了。在这上面的示例中, someFunc 中的 x 是 double 类型的,全局变量 x 是 int 类型的,但是这无关紧要, C++ 的名字隐藏准则只会这样做:隐藏名字。无论与名字相关的类型是否一致。本例中, double 类型的 x 隐藏了 int 类型的 x 。

引入继承。我们知道当我们在一个派生类的成员函数中企图引用基类的某些内容(比如成员函数、 typedef 、或者数据成员等等)时,编译器能够找出我们所引用的东西,因为派生类所继承的东西在基类中都做过声明。这里真正的工作方式实际上是:派生类的作用域嵌套在基类的作用域中。请看下面示例:

class Base {

private:

  int x;

 

public:

  virtual void mf1() = 0;

  virtual void mf2();

  void mf3();

 

  ...

};

 

class Derived: public Base {

public:

  virtual void mf1();

  void mf4();

 

  ...

};

3302.jpg
本 示例中同时存在公共的、私有的名字,另外同时包含了数据成员和成员函数的名字。成员函数包括纯需函数、简单虚函数(非纯虚的)和非虚函数。这就是向大家强 调,我们此处讨论的中心话题就是名字。示例中还可以添加进类型的名字,比如枚举类型、嵌套类以及预定义类型的名字。这里讨论的核心是:它们都是名字,而它 们是为什么东西命名的并不重要。示例中使用了单一继承结构,然而一旦你了解了
C++ 中单一继承的行为方式之后,多重继承的行为也就不难推断了。

假定继承类中 mf4 是这样实现的(部分内容):

void Derived::mf4()

{

 

  ...

  mf2();

 

  ...

}

当编译器看到这个函数中使用了 mf2 这个名字,它就能够找到 mf2 的出处。编译器是这样做到的:它通过搜寻名字为 mf2 的那处声明所在的作用域。首先它在本地作用域(也就是 mf4 以内)查找,但是没有找到任何名字为 mf2 的声明。随后编译器搜寻当前包含域,也就是 Derived 类的作用域。仍然没有找到,于是又转向搜索上一层作用域,也就是基类。在这里编译器终于找到了名叫 mf2 的东西,于是搜索结束。如果 Base 类中依然没有 mf2 ,那么搜索仍会继续,从包含 Base 的名字空间开始,到全局作用域为止。

虽然我刚刚描述的查找过程是精确的,但是其对于 C++ 中名字查找机制的描述依然没有做到面面俱到。索性我们的目标并不是对名字查找机制刨根问底从而去编写一个编译器。我们的目标是避免恼人的意外发生,针对这一点,我们掌握的信息已经足够了。

请再次考虑上面的示例,这次我们做一些小的改动:为 mf1 和 mf3 个添加一个重载版本,并且在 Derived 中为 mf3 添加一个新版本。(第 36 条中将会做出解释, Derived 中重载版本的 mf3 (一个继承的非墟函数)将会使这样的设计存在无法避免的潜在危险,但是在此问题的焦点是继承下名字的可见性,我们暂且忽略这一问题。)

class Base {

private:

  int x;

 

public:

  virtual void mf1() = 0;

   virtual void mf1(int);

 

  virtual void mf2();

 

  void mf3();

  void mf3(double);

  ...

};

 

class Derived: public Base {

public:

  virtual void mf1();

  void mf3();

  void mf4();

  ...

};

3303.jpg
这段代码的行为将会使每个乍看到它的
C++ 程序员吃上一惊。由于基于作用域的名字隐藏机制并没有改变,因此基类中所有名叫 mf1 和 mf3 的函数都被派生类中的 mf1 和 mf3 所隐藏。从名字查找的角度看, Base::mf1 和 Base::mf3 不再被 Derived 继承!

Derived d;

int x;

 

...

d.mf1();                   // 工作正常,调用 Derived::mf1

d.mf1(x);                  // 错误! Derived::mf1 隐藏了 Base::mf1

d.mf2();                   // 工作正常,调用 Base::mf2

 

d.mf3();                   // 工作正常,调用 Derived::mf3

d.mf3(x);                  // 错误! Derived::mf3 隐藏了 Base::mf3

就像你所看到的,即使同一函数在基类和派生类中的参数表不同,基类中该函数依然会被隐藏,而且这一结论不会因函数是否为虚函数而改变。类似地,在本条目最开端的实例中, someFunc 中的 double x 隐藏了全局域中的 int x ,在此, Derived 类中的函数 mf3 将 Base 类中名叫 mf3 但类型不同的函数隐藏起来。

C++ 这一特性的理论基础是:可以防止一类继承意外的发生,那就是当你为一个库或应用框架创建一个新的派生类时,你可能会去继承远族基类中的重载版本。遗憾的是,我们通常情况下恰恰希望这么做。事实上,如果你使用公有继承,但不继承重载的元素,那么就有悖于公有继承的一项基本原则——基类和派生类之间是“ Derived 是一个 Base ”关系(见第 32 条)。既然如此,你就需要时时刻刻重载 C++ 默认情况下隐藏的继承而来的名字。

这一工作通过使用 using 声明来实现:

class Base {

private:

  int x;

 

public:

  virtual void mf1() = 0;

  virtual void mf1(int);

 

  virtual void mf2();

 

  void mf3();

  void mf3(double);

  ...

};

 

class Derived: public Base {

public:

  using Base::mf1;        // 让积累中所有名为 mf1 和 mf3 的东西

  using Base::mf3;        // 在 Derived 的作用域中可见(并且是公有的)

 

  virtual void mf1();

  void mf3();

  void mf4();

  ...

};

3304.jpg
现在,继承将按部就班进行:

Derived d;

int x;

 

...

 

d.mf1();                 // 依然正常,依然调用 Derived::mf1

d.mf1(x);                // 现在可以了,调用了 Base::mf1

 

d.mf2();                 // 依然正常,依然调用 Derived::mf1

 

d.mf3();                 // 正常,调用 Derived::mf3

d.mf3(x);                // 现在可以了,调用了 Base::mf3

这意味着如果你继承一个包含重载函数的基类,并且你仅期望对其中一部分进行重定义或重载,你就应该为每一个不期望被隐藏的名字添加一条 using 声明。如果你不这样做,一些你希望继承下来的名字将可能被隐藏。

不难想象,某些场合你可能不需要把基类中所有的函数继承下来。在公有继承体系下这是无论如何不可行的,在次声明,这是违背公有继承“ Derived 是一个 Base ”关系的。(这也是为什么上文中 using 声明要置于派生类中的公共元素部分:基类中公有的名字在公共派生类中必须是公有的。)然而在私有继承体系下(参见第 39 条),这种不完全继承依然是有意义的。比如,假设 Derived 类私有地继承自 Base ,并且 Derived 只希望继承 mf1 不包含参数的那个版本。 using 声明在此就不会奏效了,因为它将使该名字所代表的所有继承版本的函数在派生类中可见。在这种情况下可以使用另一种技术,我们称之为“转发函数”:

class Base {

public:

  virtual void mf1() = 0;

  virtual void mf1(int);

 

  ...                                    // 照旧

};

 

class Derived: private Base {

public:

  virtual void mf1()                   // 转发函数 ;

  { Base::mf1(); }                     // 隐式内联(参见第 30 条)

  ...

};

 

...

 

Derived d;

int x;

 

d.mf1();                               // 正常,调用 Derived::mf1

d.mf1(x);                              // 错误! Base::mf1() 被隐藏了

内联转发函数的另一个用途是:在使用古老的编译器时,它们通常不支持使用 using 声明来为继承类的作用域引入继承的名字(这实际上是编译器的缺陷)。此时可以使用内联转发函数。

你已经了解了继承和名字隐藏的方方面面,但是当继承与模板同时使用时,又会出现“继承名字是隐藏的”一种全新的形式。 第 43 条将另起一行进行介绍。

铭记在心

l  派生类中的名字会将基类中的名字隐藏起来。在公有继承体系下,这是我们所不希望见到的。

l  为了让被隐藏名字再次可见,可以使 用 using 声明或者 转发函数。

Feedback

# re: 【读书笔记】[Effective C++第3版][第33条] 防止隐藏继承的名字  回复  更多评论   

2008-05-04 08:56 by 梦在天涯
哈哈,是的,但是C#中不会影藏!
【信息科学与工程学】【制造工程】第八十四篇 Nvidia Rubin GPU 芯片加工工艺——分层拆解表01 属性内容编号​⑮字段​N3P 节点几何尺度锚定(CPP / MxP / SRAM bit-cell area)— 用来把"3nm"从营销词翻译成可算的面积/电容/电阻预算类型​制程标尺(Scaling metrology)— FEOL+局部互连领域​工艺节点定义 · 尺度律(Dennard/ITRS-style 但不迷信"nm=physical gate length")GPU芯片​模块​整个逻辑阵列的"最小重复尺":栅方向(poly/metal-x)与鳍方向(active/OD)子模块​。 阅读详情

相关推荐

S32K144 ADC硬件触发实战:PDB模块配置与DMA传输全流程解析

本文详细解析了S32K144微控制器中ADC硬件触发的全流程实现。通过配置PDB模块产生精确的定时触发信号,并联动DMA实现转换数据的自动搬运,构建了一个无需CPU干预的高效、低抖动数据采集系统。文章深入讲解了时钟配置、模块初始化、DMA引擎搭建及实战调试技巧,为汽车电子和工业控制应用提供了可靠的解决方案。

weixin_29174141的博客 225

[译]GotW #5:Overriding Virtual Functions

虚函数是一个很基本的特性,但是它们偶尔会隐藏在很微妙的地方,然后等着你。如果你能回答下面的问题,那么你已经完全了解了它,你不太能浪费太多时间去调试类似下面的问题。 Problem JG Question 1. override和final这两个关键字都有什么作用?为什么他们有用? Guru Qusetion 2. 在你浏览公司的代码的时候,你看到了一个未知程序员写的下...

weixin_30800807的博客 111

【视频讲解】Python量子计算聚类Q-means:量子k-means算法分析电路数据实现可视化...

全文链接:https://tecdat.cn/?p=37821分析师:Yifan Zhang量子计算在近期已然成为一个频繁出现的热门概念。尽管它在大众认知以及互联网社区中备受瞩目,热度极高,然而就其实际能力而言,当前仍然存在诸多局限(点击文末“阅读原文”获取完整代码数据)。量子计算作为一个全新的领域,带来了相较于传统经典计算模式的重大范式转变。在量子计算的体系中,传统的 0 或 1 的经典比特被一...

拓端研究室TRL 1456

Effective C++ 条款33:避免遮掩继承而来的名字

深入解析 Effective C++ 条款33,理解 C++ 中名字遮掩(name hiding)的机制,掌握在继承体系中正确使用 using 声明和转交函数来避免继承名字被遮掩的问题。

凡人叶枫的博客 190

5种隐藏代码到图片的实用技巧:从Notepad++到Linux命令全解析

本文详细解析了5种将代码或文本隐藏到图片中的实用数据隐写技巧。从Notepad++尾部追加、Windows命令行合并,到编辑图像元数据、使用十六进制编辑器,再到Linux的cat/dd命令,全面覆盖了从图形界面到命令行的工程化实践方法,帮助开发者和安全研究人员掌握隐蔽信息传输与存储的核心技术。

hh234的博客 353

SCAN和JTAG分别是什么?它们之间有什么联系与区别?

SCAN(扫描)技术是集成电路测试领域的一种方法,主要用于边界扫描测试(Boundary Scan)。它是在集成电路设计中引入的一种测试结构,允许测试人员通过在芯片的输入/输出引脚上设置和读取特殊寄存器(边界扫描寄存器,BSR)的内容,来间接测试电路板上的互联和芯片内部功能块。这种方式可以在不接触其他内部节点的情况下测试整个电路板的连接性,极大地提高了测试覆盖率和可诊断性。JTAG(Joint Test Action Group,联合测试行动小组)则是开发并推广边界扫描测试标准的一个组织。

初心不忘产学研的博客 2542

Effective C++条款33:继承与面向对象之(避免遮掩继承而来的名字)

待续

董哥的黑板报 853

手把手教你给H3C MSR路由器配置4G/5G上网(保姆级图文教程)

本文提供H3C MSR路由器配置4G/5G上网的保姆级图文教程,涵盖硬件准备、运营商适配、命令行配置、多运营商策略优化及信号故障排查等全流程。通过实测数据和典型场景分析,帮助用户快速实现4G/5G模块的部署与优化,提升网络稳定性和业务连续性。

weixin_28745067的博客 437

Effective C++条款33:继承与面向对象——避免遮掩继承而来的名字

一、变量隐藏使用规则 当全局和局部存在相同的变量时,在局部作用域中,全局作用域的变量名会被隐藏,优先使用局部的变量 例如: int x; //全局变量 void someFunc() { double x; //局部变量 std::cin >> x; //局部变量赋值 ] 二、继承中的隐藏与重写(覆盖) 当基类中定义的变量/成员函数,派生类也定义同名的时,基类的变量/成员函数就会在派生类中被隐藏 演示案例 class Base { privat

www_dong的博客 165

易智联LM401模块学习(四)——双串口收发

串口的发送和接受有三种方式,分别是阻塞发送和接收(轮询)、中断方式发送和接收、DMA方式发送和接收。这里我使用阻塞+中断的方式实现双串口通信。简单的串口收发。

weixin_46046597的博客 990

【通信原理】基于【MATLAB】的通信OFDM系统仿真

主要进行OFDM系统的仿真,使用MATLAB软件对OFDM系统的接收端和发射端分别进行模拟仿真。

ALi.S的博客 5791

[翻译] Effective C++, 3rd Edition, Item 33: 避免覆盖(hiding)“通过继承得到的名字”(下)

(点击此处,接上篇)这一行为背后的根本原因是为了防止“当你在一个 library 或者 application framework 中创建一个新的 derived class 时,偶然地发生从遥远的 base classes 继承 overloads 的情况”。不幸的是,一般情况下你是需要继承这些 overloads 的。实际上,如果你使用了 public inheritance 而又没有继承

艺术编程 2618

【数字孪生与仿真技术】06:航天器系统建模与验证全流程实战:从多学科建模到数字孪生(附完整代码与工业案例)

摘要:航天器系统开发面临多学科耦合强、验证流程复杂、可靠性要求极高的挑战。本文基于MATLAB/Simulink平台,系统阐述航天器从多域建模、动力系统开发到任务验证的全流程技术框架。内容涵盖Modelica统一建模规范、光伏-电池联合供电系统仿真、基于模型的需求追溯方法、蒙特卡洛可靠性分析及硬件在环测试等核心技术,并结合James Webb太空望远镜电力系统案例,展示工业级应用实践。文中提供完整的Simulink模型代码、仿真结果及流程图,最后探讨数字孪生与AI增强技术在航天器在轨运维中的未来应用。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 1373

Effective C++ 学习笔记 条款33 避免遮掩继承来的名称

在 public 继承下,这种情况本不该发生——因为那样做会再次违反 public 继承所要求的基类与派生类之间的 is-a 关系(这就是为什么上面的 using 声明放在了派生类的 public 区域:基类中为 public 的名称,在 public 派生的派生类中也应为 public)。在这个例子中,someFunc 的 x 类型是 double,而全局的 x 类型是 int,但这并不影响结果——C++ 的名称隐藏规则就是隐藏名称,无论这些名称对应的类型是否相同。1.派生类中的名称会隐藏基类中的名称。

tus00000的博客 267

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Effective C++读书笔记(33)

条款33:避免遮掩继承而来的名称 对于继承的父类中的函数,如果在子类中有同名的函数,则父类中的同名函数都将被隐藏,例如: class Base{ private: int x; public: virtual void mf1() = 0; virtual void mf1(int x); virtual void mf2(); void mf3(); void mf3(dou

nosmatch的专栏 839

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

【一文读懂】《Effective C++ 第三版》55 个 C++ 编程黄金法则

本文是《Effective C++》第三版(Scott Meyers 著)的导读,按原书 9 章顺序逐条梳理 55 个 C++ 编程黄金法则。全书以「C++ 是四个次语言(C、OO、Template、STL)的联邦」为认知框架,涵盖习惯 C++、资源管理、构造析构赋值、接口设计、继承多态、模板泛型编程、自定义内存管理与现代标准库。每条条款以「核心思想 + 易错代码示例 + 现代 C++ 对照」呈现,适合已掌握 C++ 基本语法想写出工业级代码的开发者。

码工许师傅 338

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Effective C++

条款1:尽量用const和inline而不用#define 编译器会永远也看不到符号名 条款2:尽量用而不用

qq_33373173的博客 205

Effective C++ ——继承与面向对象设计

条款32:确定你的public继承塑模出is-a关系       以public继承的类,其父类的所有的性质都应该使用与子类,任何需要父类的地方都应该能用子类来代替,任何子类类型的对象也同时是父类的:class Person{...}; class Student : public Person{...}; void eat(const Person& p); void study(const S

ctthuangcheng 1697

effective c++

Widget W3 = W2; 调用copy constructor函数,非copy assignment函数 ab, 相当于函数调用operator(a,b) const int operator* (const int& lhs, const int& rhs) 条款1:c++是语言联邦:c、object-oriented c++、temp

dabingusa的博客 207

(Effective C++)第六章 继承与面向对象(Inheritance and Object-Oriented Design)

Public继承意味着“is-a”,virtual函数意味着“必须被继承”,non-virtual意味着“接口和实现都必须被继承”。 8.1 条款32:确定你的public继承塑模出is-a关系 (Make sure public inheritance models “is-

缘起宇轩阁 1298

SpringBoot+Vue3医院挂号系统:从架构到部署的完整实战解析

医院挂号系统是典型的高并发业务场景,核心在于号源分配与状态一致性。从单体架构到前后端分离,SpringBoot与Vue3组合凭借成熟生态和开发效率成为主流选择。文章深入剖析挂号系统架构设计,涵盖数据库建模、JWT鉴权、Redis分布式锁防超卖、Element Plus前端联动等关键环节,并结合Nginx与Docker部署实践。无论是毕业设计还是中小型医院系统选型,均可通过此方案快速落地,实现从业务模型到工程化的完整闭环。

weixin_34392435的博客 275

侯捷-C++面向对象高级开发-下

个人学习笔记

weixin_56148345的博客 353

JAVA面试题(1~95)《上》

1、面向对象的特征有哪些方面?  答:面向对象的特征主要有以下几个方面:  - 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。  - 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性...

ywl470812087的博客 4963

深挖C++:类和对象(中)

(先进后出)

xys_678的博客 234

选型笔记:Type‑C 扩展坞方案评估|DP1.4 转 HDMI2.0 桥接芯片 GSV2201S@ACP

本文记录 GSV2201S 这款 DP1.4a 转 HDMI2.0 桥接芯片评估,梳理视频、HDCP、PD 集成能力,总结 Type‑C 转接 Dongle、扩展坞方案的适配边界,以及差分走线、PD 时序、HDCP 握手等高频调试问题,为 C 口视频转接方案选型提供参考。

ACP广源盛的博客 220
上一篇: 正则表达式使用详解
下一篇: C++之父元旦专访(8+13个问题,关于C++的学习&使用和未来)
suacker
博客等级 码龄24年 46粉丝 19原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值