1. 普通作用域中的隐藏
名字实际上和继承没有关系。有关系的是作用域。我们都知道像下面的代码:
int x; // global variable void someFunc()
{
double x; // local variable std::cin >> x; // read a new value for local x }
读入x的声明指向的是本地的x而不是全局的x,因为内部作用域的名字将外部作用域的变量隐藏掉了。我们将作用域的这种情况用以下方式进行可视化:
当编译器在someFunc的作用域中时,遇到了名字x,它们先在本地作用域中查询是否有以这个名字命名的一些东西。因为存在,它们就不再检查别的作用域了。在这种情况中,someFunc的x是double类型,而global的x是int类型,这没有关系。C++的名字隐藏规则会对其进行处理:隐藏名字。不管与名字对应的是相同还是不同的类型都没有关系。在这种情况中,被命名为x的double隐藏了命名为x的int。
2. 编译器是如何在继承体系中寻找名字的
现在进入继承。我们知道当我们处在一个派生类成员函数内部时,并且引用了一些基类的东西(例如,一个成员函数,一个typedef或者一个数据成员),编译器能够找到我们所引用的东西,因为派生类继承了声明在基类中的这些东西。实际的工作方式是派生类的作用域被镶嵌在基类作用域内部。举个例子:
这个类中既有public的名字也有private的名字,既有数据成员也有成员函数。成员函数包括纯虚函数,普通虚函数(非纯虚函数)以及非虚函数。这是为了强调我们要讨论的是名字而不是别的。例子中也能包含类型名称,例如,枚举,嵌套类和typedefs。在这次的讨论中我们唯一关心的是他们是名字。他们是什么样的类型无关紧要。这个例子使用的是单继承,但是一旦你明白了在单继承下会发生什么,多继承下的C++行为很容易就能够预推测出来。
假设派生类中的mf4定义如下:
void Derived::mf4()
{
...
mf2();
...
}
当编译器看到这里使用了名字mf2,它们必须理解mf2指向的是什么。它们会在作用域中寻找名字为mf2的一个声明。首先他们在local作用域中寻找,但是发现没有任何名字为mf4的声明。接下来寻找包含(containing)作用域,也就是类Derived。它们仍然没有发现mf2,所以它们进入了下一个包含作用域,也就是基类。在这里找到了名字为mf2的声明,搜索结束了。如果在基类中没有mf2,搜索会继续进行,首先在包含Derived的namespace(s)中寻找,最后在全局作用域内寻找。
我们刚刚描述的流程是准确的,但是对于名字如何在C++中被发现来说是不全面的。我们的目标不是为了了解足够的名字寻找规则然后实现一个编译器,然而,我们应该了解足够的规则来避免碰到一些让人感到意外的事情,对这个工作,我们已经了解了大量的信息了。
3. 继承体系中的名字是如何被隐藏的
考虑前面的例子,这次我们除了要重载mf1和mf3之外,也在Derived中添加一个mf3版本。(正如Item36中解释的,Derived中mf3的声明——一个继承而来的非virtual函数——会让这个设计看起来很可疑,但是为了理解继承下的名字可见性,我们忽略这个问题。)
class Base {
private:
int x;
public:
virtual void mf1() = ;
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++程序员都感到吃惊。基于作用域的名字隐藏规则并没有变化,所以基类中的名字为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
正如你所看到的,对于有相同名字的基类和派生类中的函数有,即使参数类型不同,上面的隐藏规则也同样适用,并且它和函数的虚与非虚没有关系。在这个条款开始也是同样的方式,函数someFunc中的double x隐藏了全局作用域的int x,在这里Derived中的函数mf3隐藏了基类中名字为mf3的函数,即使参数类型不一样。
4. 如何将隐藏行为覆盖掉
这种行为背后的基本原理在于当你在一个库或者应用框架中创建一个新的派生类时,它能够阻止你突然从遥远的基类中继承重载函数。不幸的是,你总是想继承重载函数。事实上,如果你正在使用public继承并且没有继承重载函数,你就违反了基类和派生类之间的”is-a”关系,这是Item 32中介绍的public继承的基本原则。这就是基本情况,你总是想对继承来的名字的默认隐藏行为进行覆盖。
4.1 方法一 使用using 声明
使用using声明来达到这个目的:
现在的继承就按照你所期望的进行工作了:
Derived d;
int x;
...
d.mf1(); // still fine, still calls Derived::mf1
d.mf1(x); // now okay, calls Base::mf1 d.mf2(); // still fine, still calls Base::mf2 d.mf3(); // fine, calls Derived::mf3 d.mf3(x); // now okay, calls Base::mf3 (The int x is
// implicitly converted to a double so that
// the call to Base::mf3 is valid.)
这意味着如果你的类继承自包含重载函数的基类,你想对其中的一些函数进行重新定义或者覆盖,你需要为每个即将被隐藏掉的名字包含一个using声明,如果你不这样做,你想继承的一些名字就会被隐藏。
4.2 方法二 使用forwarding函数
有时候你并不想继承基类的所有函数,这是可能的。在public继承下,你应该永远拒绝这种行为,因为,它违反了基类和派生类之间public继承的”is-a”关系。(这也是为什么上面的using声明放在派生类的public部分:基类中的public名字在public继承的派生类中应该也是public的)。然而在private继承中(见Item 39),它也是有意义的。举个例子,假设Derived私有继承自基类Base,Derived类想继承基类函数mf1的唯一版本是不带参数的版本。Using声明在这里就不工作了,因为一个using声明会使得所有继承而来的函数的名字在派生类中是可见的。这里可以使用不同的技术,也就是简单的forwarding函数:
class Base {
public:
virtual void mf1() = ;
virtual void mf1(int); ... // as before };
class Derived: private Base {
public:
virtual void mf1() // forwarding function; implicitly
{ Base::mf1(); } // inline — see Item 30. (For info
... // on calling a pure virtual
}; // function, see Item 34.)
...
Derived d;
int x;
d.mf1(); // fine, calls Derived::mf1
d.mf1(x); // error! Base::mf1() is hidden
另外一种使用inline forwarding函数的地方是在使用比较老的编译器的时候,它们不支持通过using 声明来将继承而来的名字导入到派生类作用域中。
这就是关于继承和名字隐藏的全部故事,但是当继承同模板结合起来的时候,一个完全不同的“继承而来的名字被隐藏”问题就会出现,详情见 Item 43。
5. 总结
- 派生类中的名字会将基类中的名字隐藏掉。在公共继承中,这绝不是令人满意的。
- 为了让隐藏起来的名字重见天日,使用using声明或者forwarding函数来达到目的。
effective C++ Item 33 避免隐藏继承而来的名字的更多相关文章
-
读书笔记 effective C++ Item 33 避免隐藏继承而来的名字
1. 普通作用域中的隐藏 名字实际上和继承没有关系.有关系的是作用域.我们都知道像下面的代码: int x; // global variable void someFunc() { double x ...
-
Effective C++ Item 33 避免遮掩继承过来的名称
本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie ? 不懂 c++为什么derived classes 内的名称要遮掩 base classe ...
-
读书笔记 effective c++ Item 32 确保public继承建立“is-a”模型
1. 何为public继承的”is-a”关系 在C++面向对象准则中最重要的准则是:public继承意味着“is-a”.记住这个准则. 如果你实现一个类D(derived)public继承自类B(ba ...
-
读书笔记 effective c++ Item 34 区分接口继承和实现继承
看上去最为简单的(public)继承的概念由两个单独部分组成:函数接口的继承和函数模板继承.这两种继承之间的区别同本书介绍部分讨论的函数声明和函数定义之间的区别完全对应. 1. 类函数的三种实现 作为 ...
-
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 34 区分接口继承与实现继承
本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 关联条款 Item 36 接口继承和实现继承不同.在 public 继承下, derived ...
-
Effective JavaScript Item 33 让构造函数不再依赖newkeyword
本系列作为EffectiveJavaScript的读书笔记. 在将function当做构造函数使用时,须要确保该函数是通过newkeyword进行调用的. function User(name, pa ...
-
读书笔记 effective c++ Item 43 了解如何访问模板化基类中的名字
1. 问题的引入——派生类不会发现模板基类中的名字 假设我们需要写一个应用,使用它可以为不同的公司发送消息.消息可以以加密或者明文(未加密)的方式被发送.如果在编译阶段我们有足够的信息来确定哪个信息会 ...
-
Effective C++ Item 36 绝不又一次定义继承而来的 non-virtual 函数
本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:绝对不要又一次定义继承而来的 non-virtual 函数 --> Item 7 ...
随机推荐
-
mysql+mybatis+存储过程+事务 + 多并发流水号获取
数据库存储过程 drop PROCEDURE generate_serial_number; CREATE PROCEDURE generate_serial_number( ), IN param_ ...
-
OTA(空中下载技术)
选自:http://baike.baidu.com/link?url=bKOx4Gcgefi17Zt09pRA6zI-p7zzKVkoN07khRVTPfUtqyRMzdG5xURfpnp3wSP_A ...
-
VMware中第一次启动mac遇到的错误及解决方案
本文部分参考 http://blog.sina.com.cn/s/blog_938d86e90100z5ep.html 虚拟机版本:VMware-workstation-full-7.1.3-3242 ...
-
hadoop入门,跑出第一个WordCount
1.环境准备 下载:http://mirror.bit.edu.cn/apache/hadoop/common/hadoop-2.7.2/hadoop-2.7.2.tar.gz 解压:解压后,修改et ...
-
springboot 入门三- 读取配置信息二(读取属性文件方式)
在上篇文章中简单介绍自带读取方式.springboot提供多种方式来读取 一.@ConfigurationProperties(value="my") 支持更灵活的绑定及元数据的支 ...
-
20165323 实验一 Java开发环境的熟悉
一.实验报告封面 课程:Java程序设计 班级:1653班 姓名:杨金川 学号:20165323 指导教师:娄嘉鹏 实验日期:2018年4月2日 实验时间:13:45 - 15:25 实验序号:一 实 ...
-
POJ 1007 DNA Sorting(sort函数的使用)
Description One measure of ``unsortedness'' in a sequence is the number of pairs of entries that are ...
-
20162327WJH2016-2017-2《程序设计与数据结构》课程总结
20162327WJH2016-2017-2<程序设计与数据结构>课程总结 一.每周作业链接汇总 第一周作业:算法分析 第三周作业:查找与排序 第五周作业:有关栈的总结 第七周作业:树的有 ...
-
(转)详解C#中的反射
本文转载自:http://blog.csdn.net/educast/article/details/2894892 两个现实中的例子:1.B超:大家体检的时候大概都做过B超吧,B超可以透过肚皮探测到 ...
-
JSON 下 -- jansson 示例
JSON 下 —— jansson 示例 参考网址: jansson 库的下载: http://www.digip.org/jansson/ 安装jansson 步骤: http://blog.csd ...