閱讀767 返回首頁    go 技術社區[雲棲]


一道題目引發的關於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)


最後更新:2017-04-03 16:59:46

  上一篇:go 大話數據結構之三:線性表
  下一篇:go android uri規則及使用實例