今天看了《Effective C++》中的条款33,看得十分有趣。明白了以前为啥会出现那种这种问题,原来是C++中名称遮掩规则呀。具体内容见下面文章吧。
1. derived class 作用域被嵌套在base class作用域内,如下面这个例子
1 class Base {
2 private:
3 int x;
4 public:
5 virtual void mf1() = 0;
6 virtual void mf2();
7 void mf3();
8 //...
9 };
10
11 class Derived: public Base {
12 public:
13 virtual void mf1();
14 void mf4();
15 //...
16 };
17
18 void Derived::mf4()
19 {
20 //...
21 mf2();
22 //...
23 }
此时编译器是咋干活的呢?他的做法是查找各作用域,看看有没有某个名为mf2的声明式。
首先来看看一下其作用域和继承关系,如下图:
具体步骤如下:
(1)查找local作用域(此例中局势mf4覆盖的作用域),在那儿没有找到任何名为mf2的东西。
(2)查找外围作用域,也就是class Derived覆盖的作用域。还是没找到任何东西名为mf2。咋办呢?仍旧不死心呀。看(3)
(3)再往外围移动,本例为base class。在那儿它找到了一个名为mf2的东西了,于是停止查找。(终于找到了,尼玛太好了,休息咯。)
如果没有咋办呢?(4)。
(4)如果Base内还是没有mf2,先找内含Base的那个namespace的作用域(如果有的话),还是木有咋办呢?
(5)那就往global作用域找去。还是木有呢。不好意思,给你个错误,主人你没定义呀。
2.关于名称遮掩中在类继承中的更为具体的体现:
1 class Base {
2 private:
3 int x;
4 public:
5 virtual void mf1() = 0;
6 virtual void mf1(int); // overload
7 virtual void mf2();
8 void mf3();
9 void mf3(double); // overload
10 //...
11 };
12
13 class Derived: public Base {
14 public:
15 virtual void mf1();
16 void mf3(); // redefining
17 void mf4();
18 //...
19 };
20
21 /*
22 精彩部分来罗:
23 */
24 Derived d;
25 int x;
26 //...
27 d.mf1(); // ok, call Derived::mf1
28 d.mf1(x); // error, Derived::mf1遮掩了Base::mf1
29 d.mf2(); // ok, 记得上面那个编译器找东西的规则哟。调用Base::mf2
30 d.mf3(); // ok, 调用Derived::mf3
31 d.mf3(x); // error, Derived::mf3遮掩了Base::mf3
结合下图就可以知道精彩部分的解释咯。
C++的名称遮掩规则所做的唯一事情就是:遮掩名称。至于名称是否适合相同或不同的类型,并不重要。说白了,我就是看名字,管你啥子类型呀。对于上述例子来说:即使base classes 和derived classes内的函数有不同的参数类型也适用,而且不论函数是virtual或non-virtual 一体适用。
3.背后的基本理由:为了防止你在程序库或应用框架内建立新的derived class时附带地从疏远的base classes 继承重载函数(注明:重载函数只能是类内,不能再继承中体现。但是这句话理解起来好难,至今没能理解透:()。如果我想用基类的同名函数,咋办呢?使用using声明式,示例如下:
1 class Base {
2 private:
3 int x;
4 public:
5 virtual void mf1() = 0;
6 virtual void mf1(int); // overload
7 virtual void mf2();
8 void mf3();
9 void mf3(double); // overload
10 //...
11 };
12
13 class Derived: public Base {
14 public:
15 /*让Base class 内名为mf1和mf3的所有东西在Derived作用域内都可见(并且public)*/
16 using Base::mf1;
17 using Base::mf3;
18 virtual void mf1();
19 void mf3(); // redefining
20 void mf4();
21 //...
22 };
23
24 /*
25 精彩部分来罗:
26 */
27 Derived d;
28 int x;
29 //...
30 d.mf1(); // ok, call Derived::mf1
31 d.mf1(x); // ok, 调用Base::mf1
32 d.mf2(); // ok, 记得上面那个编译器找东西的规则哟。调用Base::mf2
33 d.mf3(); // ok, 调用Derived::mf3
34 d.mf3(x); // ok, 调用Base::mf3
看看使用using之后的作用域图是咋样的,如下图:
这意味如果你继承base class并加上重载函数,而你又希望重新定义或覆写(推翻)其中一部分,那么你必须为那些原本会被遮掩的每个名称引入一个using声明式,否则某些你希望继承的名称会被遮掩。
4.若想继承base classes 的部分函数,那肯定不能用public继承。如果Derived 以private形式继承Base,而Derived唯一想继承的mf1是那个无参数版本。若使用using声明式,则会继承而来的没给定名称的所有同名函数在derived class中都可见。为了解决这个问题,可以使用一个简单的转交函数。具体代码如下:
1 class Base {
2 private:
3 int x;
4 public:
5 virtual void mf1() = 0;
6 virtual void mf1(int); // overload
7 virtual void mf2();
8 void mf3();
9 void mf3(double); // overload
10 //...
11 };
12
13 class Derived: private Base { /*注意这里的继承性是private哟*/
14 public:
15 virtual void mf1()
16 {
17 Base::mf1(); // 转交函数,可以这么理解,就是交给别人处理啦。
18 }
19 void mf3(); // redefining
20 void mf4();
21 //...
22 };
23
24 /*
25 精彩部分来罗:
26 */
27 Derived d;
28 int x;
29 //...
30 d.mf1(); // ok, call Derived::mf1
31 d.mf1(x); // error, Base::mf1被遮掩了。
5.总结:
derived classes 内的名称会遮掩base classes 内的名称。在public继承下从来没有人希望如此。
为了让被遮掩的名称再见天日,可使用using申明式或转交函数。
声明:全文文字都是来源《Effective C++》 3th。这里所写都是自己的读书的时候梳理做的笔记罢了。希望对他人有用,那是我的荣幸。
所有内容都是用BSD条款。 Copyright (C) by CloudFeng.