Item 33: 避免覆盖(hiding)“通过继承得到的名字”
莎士比亚有一个关于名字的说法。"What's in a name?" 他问道,"A rose by any other name would smell as sweet."(语出《罗密欧与朱丽叶》第二幕第二场,朱生豪先生译为:“姓名本来是没有意义的;我们叫做玫瑰的这一种花,要是换了个名字,他的香味还是同样的芬芳。”梁实秋先生译为:“姓算什么?我们所谓有玫瑰,换个名字,还是一样的香。”——译者注)。莎翁也写过 "he that filches from me my good name ... makes me poor indeed."(语出《奥塞罗》第三幕第三场,朱生豪先生译为:“可是谁偷去了我的名誉,那么他虽然并不因此而富足,我却因为失去它而成为赤贫了。”梁实秋先生译为:“但是他若夺去我的名誉,于他不见有利,对我却是一件损失哩。”——译者注)。好吧,在 C++ 中,我们该用哪种态度对待通过继承得到的名字呢?
事情的实质与继承没什么关系。它与作用域有关。我们都知道它在代码中是这样的,
int x; // global variable
void someFunc()
{
double x; // local variable
std::cin >> x; // read a new value for local x
}
读入 x 的语句指涉 local 变量 x,而不是 global 变量 x,因为内层作用域的名字覆盖(“遮蔽”)外层作用域的名字。我们可以像这样形象地表示作用域的状况:

当编译器在 someFunc 的作用域中遇到名字 x 时,他们巡视 local 作用域看看是否有什么东西叫这个名字。因为那里有,它们就不再检查其它作用域。在此例中,someFunc 的 x 类型为 double,而 global x 类型为 int,但这不要紧。C++ 的 name-hiding 规则仅仅是覆盖那个名字。而相对应的名字的类型是否相同是无关紧要的。在此例中,一个名为 x 的 double 覆盖了一个名为 x 的 int。
加入 inheritance 以后。我们知道当我们在一个 derived class member
function 内指涉位于 base class 内的一件东西(例如,一个 member function,一个 typedef,或者一个
data member)时,编译器能够找到我们指涉的东西是因为 derived classes 继承到声明于 base classes
中的东西。实际中的运作方法是将 derived class 的作用域嵌套在 base class 作用域之中。例如:
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();
...
};

本例中包含的既有 public 名字也有 private 名字,既有 data members 也有
member functions。member functions 既有 pure virtual 的,也有 simple (impure)
virtual 的,还有 non-virtual
的。那是为了强调我们谈论的事情是关于名字的。例子中还可以包括其它类型的名字,例如,enums,nested classes,和
typedefs。在这里的讨论中唯一重要的事情是“它们是名字”。与它们是什么东西的名字毫不相关。这个示例中使用了 single
inheritance,但是一旦你理解了在 single inheritance 下会发生什么,C++ 在 multiple
inheritance 下的行为就很容易预见了。
假设 mf4 在 derived class 中被实现,其中一部分,如下:
void Derived::mf4()
{
...
mf2();
...
}
当编译器看到这里对名字 mf2 的使用,它就必须断定它指涉什么。它通过搜索名为 mf2 的某物的定义的作用域来做这件事。首先它在 local 作用域中搜索(也就是 mf4 的作用域),但是它没有找到被称为 mf2 的任何东西的声明。然后它搜索它的包含作用域,也就是 class Derived 的作用域。它依然没有找到叫做 mf2 的任何东西,所以它上移到它的上一层包含作用域,也就是 base class 的作用域。在那里它找到了名为 mf2 的东西,所以搜索停止。如果在 Base 中没有 mf2,搜索还会继续,首先是包含 Base 的 namespace(s)(如果有的话),最后是 global 作用域。
我刚刚描述的过程虽然是正确的,但它还不是一个关于 C++ 中名字如何被找到的完整的描述。无论如何,我们的目的不是为了充分了解关于写一个编译器时的名字搜索问题。而是为了充分了解如何避免令人吃惊的意外,而对于这个任务,我们已经有了大量的信息。
再次考虑前面的示例,而且这一次我们 overload mf1 和 mf3,并且为 Derived 增加一个 mf3 的版本。(就像 Item 36 解释的,Derived 对 mf3 ——一个通过继承得到的 non-virtual function ——的重载,使得这个设计立即变得可疑,但是出于对 inheritance 之下名字可见性问题的关心,我们就装作没看见。)
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();
...
};

以上代码导致的行为会使每一个第一次遇到它的 C++ 程序员吃惊。基于作用域的名字覆盖规则(scope-based name hiding rule)不会有什么变化,所以 base class 中的所有名为 mf1 和 mf3 的函数被 derived class 中的名为 mf1 和 mf3 的函数覆盖。从名字搜索的观点看,Base::mf1 和 Base::mf3 不再被 Derived 继承!
Derived d;
int x;
...
d.mf1(); // fine, calls Derived::mf1
d.mf1(x); // error! Derived::mf1 hides Base::mf1
d.mf2(); // fine, calls Base::mf2
d.mf3(); // fine, calls Derived::mf3
d.mf3(x); // error! Derived::mf3 hides Base::mf3
就像你看到的,即使 base 和 derived classes 中的函数具有不同的参数类型,它也同样适用,而且不管函数是 virtual 还是 non-virtual,它也同样适用。与“在本 Item 的开始处,函数 someFunc 中的 double x 覆盖了 global 作用域中的 int x”的道理相同,这里 Derived 中的函数 mf3 覆盖了具有不同类型的名为 mf3 的一个 Base 函数。
Item 33: 避免覆盖(hiding)“通过继承得到的名字”的更多相关文章
- effective C++ Item 33 避免隐藏继承而来的名字
1. 普通作用域中的隐藏 名字实际上和继承没有关系.有关系的是作用域.我们都知道像下面的代码: int x; // global variable void someFunc() { double x ...
- 读书笔记 effective C++ Item 33 避免隐藏继承而来的名字
1. 普通作用域中的隐藏 名字实际上和继承没有关系.有关系的是作用域.我们都知道像下面的代码: int x; // global variable void someFunc() { double x ...
- Effective C++ Item 33 Avoid hiding inherited names
class Base { private: int x; public: ; virtual void mf2(); void mf3(); ... }; class Derived: public ...
- Effective C++ Item 33 避免遮掩继承过来的名称
本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie ? 不懂 c++为什么derived classes 内的名称要遮掩 base classe ...
- Java 方法重载,方法重写(覆盖),继承等细节注意
1.方法重载(method overload)的具体规范 如果有两个方法的方法名相同,但参数不一致,那么可以说一个方法是另一个方法的重载. 一.方法名一定要相同. 二.方法的参数表必须不同,包括参数的 ...
- Effective C++ Item 32 确保你的 public 继承模子里出来 is-a 关联
本文senlie原版的,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:"public继承"意味 is-a.适用于 base classe ...
- Effective JavaScript Item 33 让构造函数不再依赖newkeyword
本系列作为EffectiveJavaScript的读书笔记. 在将function当做构造函数使用时,须要确保该函数是通过newkeyword进行调用的. function User(name, pa ...
- C++学习16 继承时的名字遮蔽
如果派生类中的成员变量和基类中的成员变量重名,那么就会遮蔽从基类继承过来的成员变量.所谓遮蔽,就是使用新增的成员变量,而不使用继承来的. 成员函数也一样,如果函数名和参数签名都相同,就会造成遮蔽.如果 ...
- Effective C++ Item 36 绝不又一次定义继承而来的 non-virtual 函数
本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:绝对不要又一次定义继承而来的 non-virtual 函数 --> Item 7 ...
随机推荐
- AC日记——小A的糖果 洛谷七月月赛
小A的糖果 思路: for循环贪心: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 100005 #defi ...
- AC日记——魔法森林 洛谷 P2387
魔法森林 思路: spfa水过(正解lct); 代码: #include <bits/stdc++.h> using namespace std; #define maxn 50005 # ...
- Python基础系列----环境的搭建及简单输入、输出
1.Python 以下信 ...
- 求问asp.net mvc发布问题
正常发布 浏览后如下
- 安卓APP安全测试基础
学习牛人经验,结合自己的测试,做简单总结: 简介:安卓APP安全测试目前主要覆盖以下方面:1)自身组件安全2)本地敏感数据保护3)web接口安全 一.自身组件安全目前手动.开源或免费工具均能检测此类漏 ...
- Centos Nodejs
设置Nodejs环境 第二节:Installing Node.js, PM2 and Yarn on CentOS https://www.youtube.com/watch?v=XCgCjasqEF ...
- react native 手势响应
参考地址:https://www.jianshu.com/p/935e5c6a5064 官方文档地址:https://facebook.github.io/react-native/docs/panr ...
- 一个Apache安装多个版本的PHP
我的服务器centos6.5安装了xampp,php6.5版本的.已经有好几个网站在上面运行了,但是后面要安装该死的ecshop,无奈要装php5.2,因此就想如何能在一个apache上安装多个版本的 ...
- C# CreateProcess的测试
很奇怪的一个现象,在C#中使用Process来启动进程,启动文件名必须是系统指定的扩展名.EXE,而我使用原生的Win32API ::CreateProcess ()并没有这个限制,以后遇到类似的问题 ...
- mCustomScrollbar动态加载滚动条
生成html代码之前: $(".main_body_con").mCustomScrollbar("destroy"); html添加到页面之后: $(&quo ...