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