最近在寫地瓜皮,使用命名空間同時使用友元函數的時候發生了一個神奇的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是私有成員,不能訪問。我都讓你friend了,你告訴我不能訪問,這是怎麼回事?
我們嘗試修改一下代碼,將operator>>函數也加入elephant命名空間,即,將第11行改成:
- istream& elephant::operator>>(istream& is,elephant::CA& a)
只編譯,不鏈接,報告錯誤:
# g++ -c a.cpp 它說這個函數應該在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)
本文出自 “elephant_liu” 博客,請務必保留此出處http://elephantliu.blog.51cto.com/1107116/610844