1. 程式人生 > >別再讓C++標頭檔案中出現“using namespace xxx;”

別再讓C++標頭檔案中出現“using namespace xxx;”

在這裡,我毫不迴避地說了這句話: 
引用 我再也不想在任何標頭檔案中看到“using namespace xxx;”了
作為一個開發者/團隊領導者,我經常會去招聘新的專案成員,有時候也幫助其他組的人來面試應聘者。作為應聘流程之一,我經常要求應聘者寫一些程式碼,因此我檢查過相當多的程式碼。在最近提交的C++程式碼中,我注意到一個趨勢,在任何標頭檔案中,我總是能看到以下程式碼: 

C++程式碼 
  1. using namespace std;  


如果我用我們的程式碼檢查系統(在實踐中我十分推薦這個系統)來檢驗程式碼,以上那行程式碼經常會跟著一句評論“Timo不會這樣寫的”。他們說得很對,我確實不會這麼寫。 


那麼,為什麼我說服了很多C++教材(也許並不是什麼好事),讓他們認為使用上面那段程式碼是非常壞的方式?

讓我們先來看看上面那段程式碼做了什麼。總的來說,它把名稱空間“std”以內的所有內容(或者其他由作者用using呼叫名稱空間)無一例外的引入了目前的名稱空間中。請注意我說的“所有內容”,並不是一兩個你想用的類\型別\模板。在一段程式碼的開頭引入名稱空間的原因則是加強程式模組化,和減少命名衝突。大體上,它允許你可以寫類似下面的那段程式碼,並且保證編譯器可以選擇正確的實現: 

C++程式碼 
  1. std::vector<std::string> names;  
  2. my_cool_reimplementation::vector<our_internal_stuff::string> othernames;  


現在,假定我們正在嘗試減少程式碼輸入,並且在以上程式碼中使用using宣告(或者更糟糕的,兩個名稱空間都聲明瞭),按照如下程式碼來實現: 

C++程式碼 
  1. vector<string> names;  
  2. vector<our_internal_stuff::string> othernames;  


如果這段程式碼的作者很幸運的話,編譯器會選擇vector的正確實現,或者至少在最初的階段會這麼做。但是過了一段時間,你會碰到一些很奇怪的編譯器錯誤。幸運的話,你能找到這些錯誤的原因——我曾經遇到過類似問題,我花費了好幾天才能找到這類問題的原因。該死,它們會浪費你很多的時間,僅僅因為你為了想少打5個字元的程式碼。 


並且,如果你把using宣告用在了標頭檔案中,你會讓這類問題更加惡化,因為命名衝突問題早晚都會在一個呼叫關係非常非常遠的模組中神不知鬼不覺的出現,而你可能需要查三層呼叫才可以找到原因所在,一個頭檔案包含了另一個直接使用using宣告的標頭檔案可以導致名稱空間被立刻汙染掉,任何一個使用名稱空間的檔案如果使用了std名稱空間的內容,都會導致這類問題。

那麼,為什麼你能在很多教科書中看到它們使用using namespace std?我的理論是,它確實會幫助改善整本書的排版,並且能減少一些視覺的混亂。在一本紙質書中,你只有很有限的空間來寫文字,因此你必須最大限度的利用它,加之書中的程式碼例子通常都很簡單。但另一方面,不同的名稱空間限定符會帶來了很多視覺混亂,這會讓讀者很難從上下文判斷作者的意圖。當你想在這個時代寫一些有效率的程式碼的時候,以上兩點都不完全對,現在的編譯器大多數能每行處理60-80個單詞(你可以試試,這可以的)。因此,不要亂引入名稱空間。

如果你非常明確的想在一個頭檔案中使用using宣告,應該怎麼做?我們有其他途徑可以減少不得不用using宣告的情況——你可以用以下其中一種,或則多種方式的組合。 

首先,你只需使用typedef。我會建議你使用這種方法,即使我並不經常遵循我自己的建議,我也認為無論如何這都是一個在實際應用中很好的方法。實際上,使用typedef有兩個好處——他讓一個型別名可讀性增加,如果你選擇了一個很好的名字,它可以讓作者的意圖更加顯而易見。比較一下如下的宣告方式: 

C++程式碼 
  1. std::map<std::string, long> clientLocations;  
  2. typedef std::map<std::string, long> ClientNameToZip;  
  3. ClientNameToZip clientLocations;  


第二個宣告——即使它被展開為兩行——也比第一個宣告更加直觀,同時,它也避免了名稱空間模糊化。 

另外一個選擇則是用兩種方法來限制using宣告的作用域——僅僅是你想用的那個“using”符號,例如: 

C++程式碼 
  1. using std::string;  


但是,把這段宣告扔到標頭檔案中,幾乎和使用“using namespace”一樣糟糕,因此,你應該使用作用域來限制下它的可見性,來確保你的using宣告真的只在第一次做using宣告的地方有效。例如,你可以用如下方法限制類宣告作用域: 

C++程式碼 
  1. namespace bar  
  2. {  
  3.   struct zzz  
  4.   {  
  5.     …  
  6.   };  
  7. }  
  8. class foo  
  9. {  
  10.   using namespace bar;  
  11.   zzz m_snooze; // Pulls in bar::zzz   
  12. };  


或者,你可以直接把using的作用域限制到一個函式中,例如: 

C++程式碼 
  1. void temp()  
  2. {  
  3.   using namespace std;  
  4.   string test = "fooBar";  
  5. }  


不管哪種方法,你都可以把using的作用域限制到需要使用它的程式碼中,而不是把它放到程式碼的公共空間中。你的工程越大,確保模組化,和最小化不可預料的負面影響就顯得越發重要。