相信大家对变量作用域的"名称遮掩"现象已经很熟悉了,比如下面这段代码:
int x; //global variable
void someFunc()
{
double x; //local variable
std::cin>>x; //read a new value to local x
}
这时当编译器处于someFunc的作用域内并遭遇到名称x时,它先在本作用域中查找具有x名称的变量,如果找不到再到其他作用域找.但这里的两个作用域中的具有相同名称x的变量具有不同的数据类型(someFun中的为double而global中的为int),这不是需要在意的问题.C++的名称遮掩规则只关注名称的遮掩,而名称是否具有相同的类型并不重要.下面我们来重点看一下类中的名称遮掩问题.
class Base
{
public:
virtual void mf1() = 0;
virtual void mf2();
void mf3();
...
private:
int x;
};
class Derived:public Base
{
public:
virtual void mf1();
void mf4()
{ //一个可能的实现
...
mf2();
...
}
...
};
就像独立的普通函数一样,类中的函数也具有作用域的问题,先看下面这段代码: 这段代码所构造的类作用域大体上可以用下面图示表示:
当编译器执行在mf4中遇到mf2名称的时候,编译器的做法是查找各作用域,看看有没有某个名为mf2的声明式。首先查找local作用域(也就是mf4覆盖的作用域),在那儿没找到任何东西名为mf2,。于是查找其外围作用域,也就是class Derived 覆盖的作用域,还是没找到任何东西名为mf2,于是再往外围移动,本例为base class.在那儿编译器找到一个名为mf2的东西了,于是停止查找。如果Base内还是没有mf2,查找动作便继续下去,首先找内含Base的那个namespace(s)的作用域(如果有的话),最好往global作用域找去。
class Base
{
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
...
private:
int x;
};
class Derived:public Base
{
public:
virtual void mf1();
void mf3();
void mf4();
...
};
现在我们在类中添加一下重载函数,看"名称遮掩"会怎么发生,新代码如下: 这时候作用域的大概图示可以描述如下:
我们现在写一段测试代码去test!
Derived d;
int x;
...
d.mf1(); //no problem. call Derived::mf1
d.mf1(x); //error. Derived::mf1 hiden Base::mf1
d.mf2(); //no problem. call Base::mf2
d.mf3(); //no problem. call Derived::mf3
d.mf3(x); //error. Derived::mf3 hiden Base::mf3
这种结果是不是很出乎你的意料,shit! base class内的mf1和mf3的函数都被derived class内的相应
的名字函数遮掩掉了,Base::mf1与Base::mf3竟然不再被子类继承,偶滴神啊,这种愚蠢的事情怎么能发生,
class Base{...}; //同上
class Derived:public Base{
public:
//让Base class内名为mf1与mf3的所有东西在
//Derived作用域内都可见(并且都是public)
using Base::mf1;
using Base::mf3;
....//同上
};
快快阻止它吧!这问题不大,我们可以用using声明式达成目标,我们再来看代码: 重新test上面那段代码:
Derived d;
int x;
...
d.mf1(); //no problem. call Derived::mf1
d.mf1(x); //no problem. Base::mf1
d.mf2(); //no problem. call Base::mf2
d.mf3(); //no problem. call Derived::mf3
d.mf3(x); //no problem. Base::mf3
喔也,一切ok!这意味着如果你继承base class并加上重载函数,而你又希望重新定义或覆写其中一部分,
那么你必须为那些原本被遮掩的每个名称引入一个using声明式,否则某些你希望继承的名称会被遮掩.
而有的时候你不想继承base classes的所有函数.在public继承中这显然不能发生,这违反了"base与
derived classes之间的is-a关系.",然而在private继承下它却可能是有意义的(条款39我们会详述).
例如假设Derived以private形式继承Base,而Derived唯一想继承的mf1是那个无参数版本。using 声明式在这里
派不上用场,因为using 声明式会令继承而来的某给定名称之所有同名函数在derived class中都可见。不,我们
需要不同的技术,即一个简单的转交函数(forwardng functions):
class Base
{
public:
virtual void mf1() = 0;
virtual void mf1(int);
... //same as above
};
class Derived:private Base
{
public:
virtual void mf1() //转交函数(forwarding function)
{
Base::mf1();
}
...
};
...
Derived d;
int x;
d.mf1(); //good! call Derived::mf1
d.mf1(x); //Error as suppose,Base::mf1() was hidden.
inline转交函数(forwarding function)的另一个用途是为那些不支持using声明式(注:这并非正确行为)的老旧编译器另辟一条新路,将继承而得的名称汇入derived class作用域内。
请记住:
◆ derived class内的名称会遮掩base classes内的名称.在public继承下没有人希望如此.
◆ 为了让遮掩的名称再见天日,可使用using声明式或转交函数(forwarding functions).