宏在C++中的替代解決方案
宏,在C語言中是個神的存在,能夠玩出各種花樣,也正因為此,才會給普通程式員造成不少的困擾。由於宏只在先行編譯階段起作用,使得編譯器無法檢測其中的Bug,作為新時代的C++程式員,還是遠離的好。
C++為宏提供了一些替代的解決方案,嗯,是一些。
1. 常量定義
#define NUM 100
《EffectiveC++》的第一個條款,討論的就是這個宏。由於宏是先行編譯程式來處理,所以NUM這個名字不會加入到符號表中,如果出現編譯錯誤時,提示資訊中就不會出現NUM,而是100,為排除錯誤增加了額外的障礙。
替代方案就是使用const來定義常量,或者使用枚舉enum。
const int NUM = 100;
const常量放在標頭檔中,也不必擔心存在多個執行個體的問題,對於const修飾的變數,編譯器一般也會對其進行最佳化,不會出現多重定義的問題。
C語言中還有一個特殊的常量定義:NULL。其一般的定義為 #define NULL 0,指標的內容卻是一個整型,這不符合常理。所以在C++11中使用nullptr代替了NULL。
2.函數定義
由於宏只是在代碼中做字串替代展開,所以,用宏定義的函數,實際上並沒有減少代碼的體積。另外,還有一些天然的缺陷,假設一個求平方的函數宏
#definesquare(x) (x*x)voidf(double d, int i){square(d); //OKsquare(i++); //糟糕, (i++*i++)square(d+1); //更糟,(d+1*d+1)}
縱然可以把參數加上括弧來解決,#define square(x) ((x)*(x)),但i++被執行兩次這個問題還是無法解決。
C++中的替代方案,就是使用inline函數。
inline int square(intvalue){ return value*value;}
inline函數具有函數的性質,參數傳遞不管是傳值還是傳引用,都不會對參數進行重複計算;同時會對參數做類型檢查,保證代碼的正確性;inline函數也是在代碼中做代碼展開,效率上並不比宏遜色。
如果整型不滿足需求,還可以定義為模板:
template<classT>inline T square(T& value){ return value*value;}
還有一種更離譜的函數定義形式:
#defineNull_Check(p)\if(p == NULL) return;
這種使用反斜線定義的函數,更得注意,如果在反斜線後多了個空格的話,有的編譯器會出現變異錯誤,而提示資訊嘛,你可能會困擾很久的。因為反斜線會對空格這個字元做反義,就會在代碼中的引入非法字元,人眼是很難發現這種錯誤的。
3.類型重定義
#defineDWORD unsigned int
這種類型重定義完全可以使用 typedef unsigned int DWORD 來替代。
4.條件編譯
#ifdefSystemAtestA();#else//SystemBtestB();#endif
這種條件編譯宏,一般在不同的產品或平台使用同一套代碼的情況,大量出現。定義了SystemA的時候,SystemB的代碼是不編譯的,也就意味著你的代碼沒有時刻處於編譯器的監控中。可以使用template技術來解決。
constint SystemA = 1;constint SystemB = 2; template<int T>void test(){}//定義不同的系統的特化版本template<> void test<SystemA>(){ //SystemA的實現 }template<> void test<SystemB>(){ //SystemB的實現 }
這樣,不同的系統使用自己的模板即可,別人的代碼也會同時接受編譯器的檢查,不至於出現遺漏編譯錯誤的情況。
5.標頭檔包含
#ifndeftest_h#definetest_h //test.h的實現#endif
為了防止標頭檔重複包含,C++中現在也只能這麼做,目前沒有別的替代方案。且看看原委,Bjarne Stroustrup在《C++語言的設計與演化》一書中,提供了一個include的設計,可惜的是並沒有真正實現。(Cpp, 即C語言前置處理器)
我曾經建議可以給C++本身增加一個include指示字,作為Cpp的#include的替代品。C++的這種include可以在下面三個方面與Cpp的#include不同:
1)如果一個檔案被include兩次,第二個include將被忽略。這解決了一個實際問題,而目前這個問題是通過#define和#ifdef,以非常低效而笨拙的方式處理的。
2)在include的本文之外定義的宏將不在include的本文內部展開。這就提供了一種能夠隔離資訊的機制,可以使這些資訊不受宏的幹擾。
3)在include的本文內容定義的宏在include本文之後的本文處理中不展開。這保證了include本文內部的宏不會包含它的編譯模組強加上某種順序依賴性,並一般地防止了由宏引起的奇怪情況。
對於採用先行編譯標頭檔的系統而言一般地說,對於那些要用獨立部分組合軟體的人們而言,這種機制都將是一個福音。請注意,無論如何這還只是一個思想而不是一個語言特徵。
也就是說,這個想法在C++中並沒有實現。
如果你沒有很好的駕馭宏,那就敬而遠之吧。