条款33:避免遮掩继承而来的名称
C++中我们需要注意作用域问题,尤其在继承体系中,可能存在覆盖问题,先看一下如下代码,了解一下作用域概念:
int x;
void someFunc()
{
double x;
std::cin>>x;
}
其作用域表示为:
可以看到当编译器处于someFunc函数的作用域中并遇见x时候,她现在local作用域内查找x,如果找不到在向外层作用域查找,如果还是不行继续向外层拓展,因此内部的x覆盖了外部x的值!
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();
...
};
void Derived::mf4()
{
...
mf2();
...
}
作用域:
由于Derived classes继承了声明于Base classes内的所有东西,实际运作方式是,derived class作用域被嵌套在base class作用域内,derived class作用域中的值可能会覆盖掉base class作用域中的值!正如上面的代码,编译器遇到mf2()的时候,首先查找其外围作用域,也就是class Derived覆盖的作用域,没有找到,则想外层继续查找,这里指的是class Base作用域,如果还是没有的话则继续向外即查找Base的那个namespace的作用域,如果还是没有找到,最后往global作用域中查找!
问题:继承体系中的作用域遮掩问题
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();
...
};
Derived d;
int x;
...
d.mf1();
d.mf1(x);//错误,因为Derived::mf1遮掩了Base::mf1
d.mf2();
d.mf3();
d.mf3(x);//错误,因为Derived::mf3遮掩了Base::mf3
如何解决这个问题呢?
1)通过using声明式可以比较完整的解决该问题,代码如下:
class Base{
public:
virtual void mf1()=0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
...
};
class Derived:private Base{
public:
using Base::mf1;
using Base::mf3;
virtual void mf1();
void mf3();
void mf4();
...
};
Derived d;
int x;
...
d.mf1();
d.mf1(x);//ok,调用Base::mf1
d.mf2();
d.mf3();
d.mf3(x);//ok,调用Base::mf3
这种方式当然没有问题,通过using声明将基类中的相应的函数提升到子类中,保证子类中的不被覆盖,可以看看如下的代码和运行结果进行深入理解:
#include <iostream>
#include <string>
using namespace std;
class Base{
public:
Base(int x) :x(x){
}
void mf1(){
cout << "Base::mf1()" << endl;
};
void mf1(int h){
cout << "这是Base中的mf1(int h)===>" <<h<< ",带参数哟!" << endl;
}
void mf2(){
cout << "Base::mf2()" << endl;
};
void mf3(){
cout << "Base::mf3()" << endl;
};
void mf3(double d){
cout << "这是Base中的mf3(double d)===>" << d << ",带参数哟!" << endl;
}
private:
int x;
};
class Derived :public Base{
public:
Derived(int x) :Base(x){
}
using Base::mf1;
using Base::mf3;
void mf1(){
cout << "Derived::mf1()" << endl;
}
void mf3(){
cout << "Derived::mf3()" << endl;
}
void mf4(){
cout << "Derived::mf4()" << endl;
}
};
int main(){
Derived d(10);
d.mf1();
d.mf3(100.00);
d.mf1(10);
return 0;
}
运行结果:
这种方式意味着如果你继承了Base class并加上重载函数,而你又希望重新定义或者覆写其中一部分,那么你必须为那些原本被这样的每个名称引入一个using声明式,否则某些你希望继承的名称会被遮掩。
2)如果有时候你嫌弃Base class中的所有函数都继承的话显得相当累赘,我们并不想继承所有的函数,这个时候应该怎么解决呢?在public继承中这种根本不可能出现,因为public继承满足is-a条例,此时我们可以通过private继承机制完成,using声明在这儿就没法使用了,因为using声明会令继承而来的某给定名称之所有的同名函数在derived class中都可见!假设现在Derived class指向继承Base class中的void mf1()函数,并不想继承带参数的麻烦,不能使用public继承吧,同时不能使用using声明吧,因为这样会使得继承而来的某给定名称之所有的同名函数在derived class中都可见,所以,我们通过转交函数实现:
class Base{
public:
virtual void mf1()=0;
virtual void mf1(int);
...
};
class Derived:private Base{
public:
virtual void mf1(){ //转交函数(forwarding function)
Base::mf1();//暗自成为inline函数
}
...
};
Derived d;
int x;
d.mf1();//ok
d.mf1(x);//Error,Base::mf1(int)被遮掩了
inline转交函数的另一个用途是为那些不支持using声明式的老编译器开辟一条新路,将继承而得的名称汇入derived class作用域内。
总结:
- derived class内的名称会遮掩base class中的名称,在public继承下从来没有人希望如此;
- 为了让这样的名称重见天日,可使用using声明式或者转交函数。