一道题目引发的关于c++命名域的问题--Avoid hiding inheried names

那天在一问一答上碰到一道题:

下面程序的输出?

#include <iostream>
using namespace std;
static int x = 1;
static int y = 2;
struct A {
    static int x;
    static int y;
};
int A::x = 3;
int A::y = x;
int main(void) {
    cout << A::y << endl;
    return 0;
}
先不说题目的正确答案,我当时的思路是 碰到int A::y =x 这条语句时,是将全局变量x的值赋给了结构体A中的成员变量y,所以应该是1,但是实际并不是这样的(也许题目看起来是这样的,但其实里面隐藏着一个陷阱),在得到正确答案之后,我想起来曾经看过的<<Effective C++>>一书当中,关于这部分内容的详细解答:

在诸如以下的代码中:

int x;    //global variable

int SomeFun(){
    double x;
    std::cin>>x;
}
这个读取数据的语句涉及的是local变量x,而不是global变量,因为内层作用于的名字会掩盖(遮盖)外围作用于的名字

当编译器处于SomeFun的作用域内并遭遇x时,它首先在local作用域内寻找是否有什么东西带着这个名称,如果找不到,就继续依次向外围作用于查找,如果找到,就不再找其他作用域。 本例中的local  x是double的,但是global中x是int型的,但是不要紧,C++的名称掩盖规则(name-hiding rules)唯一做的一件事是:掩盖名称。至于类型是否相同,并不重要。于是本例中,一个double型的x掩盖了一个int型的x。

我们再来看一个复杂点的例子:

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++的以作用域为基础的名称掩盖规则并没有改变,因此base class内的所有的名为mf1 和mf3的函数都被derived class内的mf1 和mf3翟盖掉了,从名称查找观点来看,Base:: mf1和Base::mf3不再被Derived class继承!!!

Derived d;
int x;
...
d.mf1();    //correct! 调用dervied类的mf1
d.mf1(x);  // Wrong! 因为Derived类的mf1掩盖了Base类的mf1
d.mf2();    //correct! 调用Base::mf2
d.mf3();    //correct! 调用Derived::mf3
d.mf3(x);  //Wrong! 
如我们所看到的,即使base class和derived class内的函数有不同的参数类型也适用,而且不论函数是virtual还是non-virtual一体适用。

这些行为的背后的基本理由是为了防止你在程序库 或者应用框架(application framework)内建立新的derived class时附带的从疏远的base class继承重载函数。而实际上如果我们正在适用public继承而又不继承那些重载函数,就违反了base和derived class之间的is-a 关系,而  is-a 是public继承的基石,为此,我们可以适用using声明式达成目标:

class Derived: public Base{
public:
    using Base::mf1;    //让Base class内名为mf1和mf3的所有东西在
    using Base::mf3;    //Derived作用域内都可见
    virtual void mf1();
    virtual mf3();
    void mf4();
    ...
};

现在所有的继承机制就可以正常运行了!

有时候我们并不想继承base class内的所有函数,这也是可以理解的,但是要记住,在public继承下,这是绝对不可能的,因为它违反了public继承下derived 和base class之间的is-a 关系,(这也就是为什么上述using声明式呗放在derived class的public区域的原因,base class内的public名称在publicly derived class内也应该是public)。

然而在private继承下,它可能有意义。例如derived以private继承自base,儿derived唯一想继承的mf1是那个无参数版本。using声明式在此处排不上用处了,因为它会令继承而来的某给定名称的所有同名函数在derived class内为可见。因此我们需要一个简单的转交函数(forwarding function)

class Derived: private Base{
public:
    virtual void mf1()
    {Base::mf1();}    //转交函数,暗自成为inline函数
    ...
};

Derived d;
int x;
d.mf1();    //正确,调用derived::mf1
d.mf1(x);  //错误,因为base::mf1被掩盖了

1. derived class内的名称会遮掩base class内的名称,在public继承下没有人希望如此

2. 为了让掩盖的名称再见天日,可使用using声明式货转交函数(forwarding function)


上一篇:利用流动态建立二维数组


下一篇:[算法]单链表之和