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 函数。

时间: 2024-10-13 02:16:51

Item 33: 避免覆盖(hiding)“通过继承得到的名字”的相关文章

Effective C++ Item 33 避免遮掩继承过来的名称

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie ? 不懂 c++为什么derived classes 内的名称要遮掩 base classes 内的名称. 经验:derived classes 内的名称会遮掩 base classes 内的名称.在 public 继承下从来没有人希望如此. C++ 的名称遮掩规则所做的唯一事情就是: 遮掩名称 derived class 作用域被嵌套在 base class 作用域里 class Bas

Effective C++ Item 32 确定你的 public 继承塑模出 is-a 关系

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:"public继承"意味 is-a.适用于 base classes 身上的每一件事情一定也适用于 derived classes 身上, 因为每一个 derived classes 身上,因为每一个 derived class 对象也都是一个 base class 对象. 示例: class Person {...}; class Student: public Pers

Java 方法重载,方法重写(覆盖),继承等细节注意

1.方法重载(method overload)的具体规范 如果有两个方法的方法名相同,但参数不一致,那么可以说一个方法是另一个方法的重载. 一.方法名一定要相同. 二.方法的参数表必须不同,包括参数的类型或个数,以此区分不同的方法体. 1.如果参数个数不同,就不管它的参数类型了! 2.如果参数个数相同,那么参数的类型或者参数的顺序必须不同. 三.方法的返回类型.修饰符可以相同,也可不同. 四.main方法也可以被重载 方法重载的作用:可以一个相同的方法传入不同的参数以达到想要的结果 2.方法继承

Effective JavaScript Item 33 让构造函数不再依赖new关键字

本系列作为EffectiveJavaScript的读书笔记. 在将function当做构造函数使用时,需要确保该函数是通过new关键字进行调用的. function User(name, passwordHash) { this.name = name; this.passwordHash = passwordHash; } 如果在调用上述构造函数时,忘记了使用new关键字,那么: var u = User("baravelli", "d8b74df393528d51cd19

JAVA中方法重载,方法覆盖,方法继承等小结

1.方法重载(method overload)的具体规范 如果有两个方法的方法名相同,但参数不一致,哪么可以说一个方法是另一个方法的重载. 一.方法名一定要相同. 二.方法的参数表必须不同,包括参数的类型或个数,以此区分不同的方法体. 1.如果参数个数不同,就不管它的参数类型了! 2.如果参数个数相同,那么参数的类型或者参数的顺序必须不同. 三.方法的返回类型.修饰符可以相同,也可不同. 四.main方法也可以被重载

Effective C++ Item 36 绝不重新定义继承而来的 non-virtual 函数

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:绝对不要重新定义继承而来的 non-virtual 函数 --> Item 7 "为多态基类声明 virtual 析构函数" 是本条款的特例 示例: class B{ public: void mf(); //... }; class D: public B{ public: void mf(); // 遮掩了B::mf,Item 33 名称遮掩规则 } D x; B

[转]Java中继承、多态、重载和重写介绍

什么是多态?它的实现机制是什么呢?重载和重写的区别在那里?这就是这一次我们要回顾的四个十分重要的概念:继承.多态.重载和重写. 继承(inheritance) 简单的说,继承就是在一个现有类型的基础上,通过增加新的方法或者重定义已有方法(下面会讲到,这种方式叫重写)的方式,产生一个新的类型.继承是面向对象的三个基本特征--封装.继承.多态的其中之一,我们在使用JAVA时编写的每一个类都是在继承,因为在JAVA语言中,java.lang.Object类是所有类最根本的基类(或者叫父类.超类),如果

javascript面向对象(三)--继承

1 //继承: ECMAScript只支持 实现继承 依靠原型链来实现 2 //一.原型链 3 //先来理清下关系: 4 //每个构造函数都会有一个原型对象 Person --> prototype 5 //每一个原型对象都会有一个指向构造函数的指针 Person.prototype.constructor --> Person 6 //每一个实例都包含一个指向原型对象的内部指针 person1 --> prototype 7 //当原型对象等于另一个类的实例的时候,那么原型对象就会包含

C++基础6 【继承】 类型兼容 satatic 多继承 虚继承 【多态】 案例 虚析构函数 重载重写重定义

[继承] 继承的访问控制域 图 类型兼容性原则  指针 与 引用 用子类直接初始化父类 类的继承模型示意 图 [继承结论] [非常重要的概念] 继承与组合混搭情况下,构造和析构调用原则 原则:先构造父类,再构造成员变量.最后构造自己 先析构自己,在析构成员变量.最后析构父类 继承中,同名的成员变量的处理办法 继承中,同名的成员函数的处理办法 派生类中的static关键字 如果静态成员变量,你没有使用,也没有初始化的话 编译不会报错 经典错误 : 类中函数默认是private的,无法在外部访问 具