最近在寫地瓜皮,使用名稱空間同時使用友元函式的時候發生了一個神奇的compile error,經過思考,終於將問題解決了。現在釋出出來,希望能夠對大家有所幫助。

     先簡單說說名稱空間。在寫C工程的時候,尤其是萬行以上的程式,名稱空間衝突是一個很讓人崩潰的事情。目前使用的最多的解決辦法就是把函式的名字搞的非常非常長(學過GTK的同學應該有所體會)。在C++中,增加了一個叫做“名稱空間”的特性,它由關鍵字“namespace”來宣告、定義和使用。相信學過C++的同學對“名稱空間”的使用方法都有了比較深入的理解,我在此就不再介紹了。

     而當我們有時需要過載類的某些運算子的時候,又難以避免地用到“友元函式”。曾經有過大牛批判過“友元函式”破壞類的封裝性。不過對於我這種低水平的coder,“友元函式”確實帶來了不少方便的地方。

    問題一:

    先給大家看一個簡單的程式碼:

  1. class istream; 
  2. istream& operator>>(istream&,int&); 
  3. class CA 
  4.     int a;
  5.     friend istream& operator>>(istream&,CA& ); 
  6. }; 
  7. istream& operator>>(istream& is,CA& a) 
  8.     is>>a.a; 
  9.     return is; 
  10. }
  11. int main() 
  12.    extern istream stream; 
  13.    elephant::CA a; 
  14.    stream>>a;
  15. }

     儲存為a.cpp之後編譯(不要連結)。

  g++ -c a.cpp

     由於這裡沒有istream這個類的定義,所以連結肯定會失敗。使用-c選項只編譯不連結。

    這時編譯沒有任何問題。

    當我想把CA這個類包含在一個elephant命令空間後,出現問題了。用程式碼說話:

 

  1. class istream; 
  2. istream& operator>>(istream&,int&); 
  3. namespace elephant 
  4.     class CA 
  5.     { 
  6.         int a;
  7.         friend istream& operator>>(istream&,CA& ); 
  8.     }; 
  9. istream& operator>>(istream& is,elephant::CA& a) 
  10.     is>>a.a; 
  11.     return is; 
  12. int main() 
  13.    extern istream stream; 
  14.    elephant::CA a; 
  15.    stream>>a;
  16. }

    同樣,只編譯,不連結,報告錯誤:

#  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行改成:

 

  1. 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一下。完整程式碼如下:
   

  1. class istream; 
  2. istream& operator>>(istream&,int&); 
  3. namespace elephant 
  4.     class CA 
  5.     { 
  6.         int a;
  7.         friend istream& operator>>(istream&,CA& ); 
  8.     }; 
  9.     istream& operator>>(istream&,CA&); 
  10. istream& elephant::operator>>(istream& is,elephant::CA& a) 
  11.     is>>a.a; 
  12.     return is; 
  13. int main() 
  14.     extern istream stream; 
  15.     elephant::CA a; 
  16.     stream>>a; 

    只編譯,不連結。編譯成功。

    總結:

        1、friend沒有對函式進行宣告,所以我們要另外宣告一下這個函式。friend僅僅是告訴這個類,這個函式對這個類的私有成員有訪問許可權。

        2、在名稱空間內部進行宣告的函式已經被包含進了名稱空間,在定義的時候要使用名稱空間說明符。

        3、friend僅僅說明了elephant::operator>>函式是類CA的友元函式,而operator>>函式並不是類CA的友元函式。所以operator>>訪問類CA的私有成員,編譯器會報告錯誤。

        4、elephant::operator>>與operator>>並不是一個函式。前者是定義在elephant名稱空間下的函式,而後者則是定義在全域性的函式。

 

    問題二:

        還是一個簡單的程式碼:

 

  1. void output(int a); 
  2. class SA 
  3.     int data; 
  4.     friend void output(const SA&); 
  5. }; 
  6. void output(const SA& a) 
  7.     output(a.data); 
  8. int main() 
  9.     SA a; 
  10.     output(a); 

    只編譯,不連結。沒有任何問題。現在,同樣的,我要將類SA放在elephant名稱空間裡。根據問題一的討論,我們也得把output函式放在elephant名稱空間裡。程式碼如下:

 

  1. namespace elephant 
  2.     class SA 
  3.     { 
  4.         int data; 
  5.         friend void output(const SA&); 
  6.     }; 
  7.     void output(int a); 
  8.     void output(const SA&); 
  9. void elephant::output(const elephant::SA& a) 
  10.     output(a.data); 
  11. int main() 
  12.     elephant::SA a; 
  13.     elephant::output(a); 

    只編譯,不連結。沒有任何問題。

    可是,在我的地瓜皮裡面,由於某種特殊的需要,我希望output函式及它所有的過載函式全都定義在全域性空間裡。怎麼辦?

    我進行了兩次嘗試。第一個嘗試是失敗的,上程式碼:

   

  1. void output(int a); 
  2. namespace elephant 
  3.     class SA; 
  4. void output(const elephant::SA&); 
  5. namespace elephant 
  6.     class SA 
  7.     { 
  8.         int data; 
  9.         friend void output(const SA&); 
  10.     }; 
  11. void output(const elephant::SA& a) 
  12.     output(a.data); 
  13. int main() 
  14.     elephant::SA a; 
  15.     output(a); 

    由於output函式需要用到elephant::SA型別的引數,所以對elephant::SA做了一個前向宣告。但是,明顯的,這是不行的。根據問題一的討論,elephant::output對elephant::SA的私有成員有訪問許可權,而output沒有。

    如何解決這個問題?或者說,如何將全域性空間的output函式宣告為elephant::SA的友元函式。答:使用全域性空間說明符。上程式碼:

   

  1. void output(int a); 
  2. namespace elephant 
  3.     class SA; 
  4. void output(const elephant::SA&); 
  5. namespace elephant 
  6.     class SA 
  7.     { 
  8.         int data; 
  9.         friend void ::output(const SA&); 
  10.     }; 
  11. void output(const elephant::SA& a) 
  12.     output(a.data); 
  13. int main() 
  14.     elephant::SA a; 
  15.     output(a); 

    只編譯,不連結。成功。

    “::”前面是一個空格,這個叫做全域性空間說明符。相信大家在學習C++的時候已經學過。在此不再詳細解釋。

    總結:

        1、在名稱空間內定義類的友元函式的時候,它預設是指向相同名稱空間內的函式。

        2、如需指向全域性空間內的函式,需要使用全域性空間說明符說明。

 

 

    相關資訊:

        作業系統:ubuntu linux 10.10 maverick

        編譯器:g++ version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)