您的位置:首页 > 编程语言 > C语言/C++

读书笔记《Effective C++》条款33:避免遮掩继承而来的名称

2017-05-24 23:50 531 查看
我们知道在诸如这般的代码中:

int x;//global变量
void someFunc()
{
double x;//local变量
std::cin >> x;//读一个新值赋予local变量x
}

这个读取数据的语句指涉的是local变量x,而不是global变量x,因为内层作用域的名称会遮掩外围作用域的名称。如下图所示:






当编译器处于someFunc的作用域内并遭遇名称x时,它在local作用域内查找是否有什么东西带着这个名称。如果找到就不再找其他作用域。本例的someFunc的x是double类型而global x是int类型,但那不要紧。C++的名称遮掩规则所做的唯一事情就是:遮掩名称。至于名称是否应和相同或不同的类型,并不重要。本例中一个名为x的double遮掩了一个名为x的int。

现在我们来看继承。每个类都保持着自己的作用域,在该作用域中定义了成员的名字。在继承情况下,派生类的作用域嵌套在基类作用域中。如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义。

当位于一个derived class成员函数内指涉base class内的某物时,编译器可以找出我们所指的东西,因为derived class继承了声明于base class内的所指东西。实际运作方式是: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名称,以及一组成员变量和成员函数名称。这些成员函数包括pure virtual,impure virtual和non-virtual三种,这是为了强调我们谈的是名称,和其他无关。这个例子中也可以加入各种名称类型,例如enum,nested class和typedef。整个讨论中唯一重要的是这些东西的名称。
假设Derived class内的mf4的实现代码部分像这样:

void Derived::mf4()
{
mf2();
}当编译器看到这里使用名称mf2,必须估算它是什么。编译器的做法是查找各作用域,看看有没有某个名为mf2的声明。首先查找local作用域(也就是函数mf4覆盖的作用域),如果没有找到任何东西名为mf2。于是查找其外围作用域,也就是Derived class覆盖的作用域。还是没有找到,于是再往外围移动,本例为Base class覆盖的作用域,在Base class作用域编译器找到了一个名为mf2的东西了,于是停止查找。如果Base class内还是没有mf2,查找动作便继续下去,首先找内含Base的那个namespace的作用域(如果有的话),最后往global作用域去查找。
再次考虑前一个例子,我们重载mf1和mf3,并且添加一个新版mf3到Derived去。如下:

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();
};


以作用域为基础的“名称掩盖规则”并没有改变,因此base class内所有名为mf1和mf3的函数都被Derived class内的mf1和mf3函数遮掩掉了。从名称查找观点来看,Base::mf1和Base::mf3不再被Derived继承!
Derived d;
int x;

d.mf1();//正确,调用Derived::mf1
d.mf1(x);//错误,因为Derived::mf1遮掩了Base::mf1,在Derived作用域中只有1个无参函数mf1(),没有参数匹配的重载函数mf1(int)
d.mf2();//正确,调用Base::mf2
d.mf3();//正确,调用Derived::mf3
d.mf3(x);//错误,因为Derived::mf3遮掩了Base::mf3,在Derived作用域中只有1个无参函数mf3(),没有参数匹配的重载函数mf3(int)如上所见,上述规则都适用。即使Base class和Derived class内的函数有不同的参数类型也适用,而且不论函数是virtual或non-virtual一体适用。这和本条款一开始展示的道理相同。Derived内的函数mf3遮掩了一个名为mf3但类型不同的Base函数。
重载是指同一作用域,函数名称相同,参数类型不同。[b]每一个版本的重载函数都应该在同一个作用域中声明。返回值类型不同不能作为重载依据,如果函数名称相同,参数类型也相同,但返回值类型不同,编译器会发出错误。[/b]

继承体系中,如果Derived中有与Base中名称相同的函数(其它不管,不论函数参数类型,virtual或者non-virtual),发生的都不是重载,而是遮掩行为!

这些行为背后的基本理由是为了防止你的程序库或者应用框架内建立新的Derived class时附带地从疏远的Base class继承重载函数。

Derived中可以使用using声明式,达到调用Base中同名函数的调用。修改如下:

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:
using Base::mf1;//让Base class内名为mf1和mf3的所有东西在Derived作用域内都可见(并且public)
using Base::mf3;
virtual void mf1();
void mf3();
void mf4();
};现在,继承机制将一如往昔地运作:
Derived d;
int x;

d.mf1();//正确,调用Derived::mf1
d.mf1(x);//现在没有问题了,调用Base::mf1
d.mf2();//正确,调用Base::mf2
d.mf3();//正确,调用Derived::mf3
d.mf3(x);//现在没有问题了,调用Base::mf3这意味如果你继承Base并加上重载函数,而你又希望重新定义或覆写其中一部分,那么你必须为那些原本会被遮掩的每个名称引入一个using声明式,否则那些你希望继承的名称会被遮掩。

要点:

1.Derived class内的名称会遮掩Base class内的名称。在public继承下从来没有人希望如此。

2.为了让被遮掩的名称再见天日,可使用using声明式或转交函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: