最近在寫地瓜皮,使用名稱空間同時使用友元函式的時候發生了一個神奇的compile error,經過思考,終於將問題解決了。現在釋出出來,希望能夠對大家有所幫助。
先簡單說說名稱空間。在寫C工程的時候,尤其是萬行以上的程式,名稱空間衝突是一個很讓人崩潰的事情。目前使用的最多的解決辦法就是把函式的名字搞的非常非常長(學過GTK的同學應該有所體會)。在C++中,增加了一個叫做“名稱空間”的特性,它由關鍵字“namespace”來宣告、定義和使用。相信學過C++的同學對“名稱空間”的使用方法都有了比較深入的理解,我在此就不再介紹了。
而當我們有時需要過載類的某些運算子的時候,又難以避免地用到“友元函式”。曾經有過大牛批判過“友元函式”破壞類的封裝性。不過對於我這種低水平的coder,“友元函式”確實帶來了不少方便的地方。
問題一:
先給大家看一個簡單的程式碼:
- class istream;
- istream& operator>>(istream&,int&);
- class CA
- {
- int a;
- friend istream& operator>>(istream&,CA& );
- };
- istream& operator>>(istream& is,CA& a)
- {
- is>>a.a;
- return is;
- }
- int main()
- {
- extern istream stream;
- elephant::CA a;
- stream>>a;
- }
儲存為a.cpp之後編譯(不要連結)。
g++ -c a.cpp |
由於這裡沒有istream這個類的定義,所以連結肯定會失敗。使用-c選項只編譯不連結。
這時編譯沒有任何問題。
當我想把CA這個類包含在一個elephant命令空間後,出現問題了。用程式碼說話:
- class istream;
- istream& operator>>(istream&,int&);
- namespace elephant
- {
- class CA
- {
- int a;
- friend istream& operator>>(istream&,CA& );
- };
- }
- istream& operator>>(istream& is,elephant::CA& a)
- {
- is>>a.a;
- return is;
- }
- int main()
- {
- extern istream stream;
- elephant::CA a;
- stream>>a;
- }
同樣,只編譯,不連結,報告錯誤:
# g++ -c a.cpp a.cpp: In function ‘istream& operator>>(istream&, elephant::CA&)’: a.cpp:7: error: ‘int elephant::CA::a’ is private a.cpp:13: error: within this context |
說啥?說a是私有成員,不能訪問。我都讓你friend了,你告訴我不能訪問,這是怎麼回事?
我們嘗試修改一下程式碼,將operator>>函式也加入elephant名稱空間,即,將第11行改成:
- istream& elephant::operator>>(istream& is,elephant::CA& a)
只編譯,不連結,報告錯誤:
# g++ -c a.cpp a.cpp:11: error: ‘istream& elephant::operator>>(istream&, elephant::CA&)’ should have been declared inside ‘elephant’ |
它說這個函式應該在elephant名稱空間裡面declare一下(相信大家能夠區分declare和define的區別,前一個是“宣告”,後一個是“定義”)。那麼我們就declare一下。完整程式碼如下:
- class istream;
- istream& operator>>(istream&,int&);
- namespace elephant
- {
- class CA
- {
- int a;
- friend istream& operator>>(istream&,CA& );
- };
- istream& operator>>(istream&,CA&);
- }
- istream& elephant::operator>>(istream& is,elephant::CA& a)
- {
- is>>a.a;
- return is;
- }
- int main()
- {
- extern istream stream;
- elephant::CA a;
- stream>>a;
- }
只編譯,不連結。編譯成功。
總結:
1、friend沒有對函式進行宣告,所以我們要另外宣告一下這個函式。friend僅僅是告訴這個類,這個函式對這個類的私有成員有訪問許可權。
2、在名稱空間內部進行宣告的函式已經被包含進了名稱空間,在定義的時候要使用名稱空間說明符。
3、friend僅僅說明了elephant::operator>>函式是類CA的友元函式,而operator>>函式並不是類CA的友元函式。所以operator>>訪問類CA的私有成員,編譯器會報告錯誤。
4、elephant::operator>>與operator>>並不是一個函式。前者是定義在elephant名稱空間下的函式,而後者則是定義在全域性的函式。
問題二:
還是一個簡單的程式碼:
- void output(int a);
- class SA
- {
- int data;
- friend void output(const SA&);
- };
- void output(const SA& a)
- {
- output(a.data);
- }
- int main()
- {
- SA a;
- output(a);
- }
只編譯,不連結。沒有任何問題。現在,同樣的,我要將類SA放在elephant名稱空間裡。根據問題一的討論,我們也得把output函式放在elephant名稱空間裡。程式碼如下:
- namespace elephant
- {
- class SA
- {
- int data;
- friend void output(const SA&);
- };
- void output(int a);
- void output(const SA&);
- }
- void elephant::output(const elephant::SA& a)
- {
- output(a.data);
- }
- int main()
- {
- elephant::SA a;
- elephant::output(a);
- }
只編譯,不連結。沒有任何問題。
可是,在我的地瓜皮裡面,由於某種特殊的需要,我希望output函式及它所有的過載函式全都定義在全域性空間裡。怎麼辦?
我進行了兩次嘗試。第一個嘗試是失敗的,上程式碼:
- void output(int a);
- namespace elephant
- {
- class SA;
- }
- void output(const elephant::SA&);
- namespace elephant
- {
- class SA
- {
- int data;
- friend void output(const SA&);
- };
- }
- void output(const elephant::SA& a)
- {
- output(a.data);
- }
- int main()
- {
- elephant::SA a;
- output(a);
- }
由於output函式需要用到elephant::SA型別的引數,所以對elephant::SA做了一個前向宣告。但是,明顯的,這是不行的。根據問題一的討論,elephant::output對elephant::SA的私有成員有訪問許可權,而output沒有。
如何解決這個問題?或者說,如何將全域性空間的output函式宣告為elephant::SA的友元函式。答:使用全域性空間說明符。上程式碼:
- void output(int a);
- namespace elephant
- {
- class SA;
- }
- void output(const elephant::SA&);
- namespace elephant
- {
- class SA
- {
- int data;
- friend void ::output(const SA&);
- };
- }
- void output(const elephant::SA& a)
- {
- output(a.data);
- }
- int main()
- {
- elephant::SA a;
- output(a);
- }
只編譯,不連結。成功。
“::”前面是一個空格,這個叫做全域性空間說明符。相信大家在學習C++的時候已經學過。在此不再詳細解釋。
總結:
1、在名稱空間內定義類的友元函式的時候,它預設是指向相同名稱空間內的函式。
2、如需指向全域性空間內的函式,需要使用全域性空間說明符說明。
相關資訊:
作業系統:ubuntu linux 10.10 maverick
編譯器:g++ version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)