巨集在C++中的替代解決方案
巨集,在C語言中是個神的存在,能夠玩出各種花樣,也正因為此,才會給普通程式設計師造成不少的困擾。由於巨集只在預編譯階段起作用,使得編譯器無法檢測其中的Bug,作為新時代的C++程式設計師,還是遠離的好。
C++為巨集提供了一些替代的解決方案,嗯,是一些。
- 常量定義
#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.函式定義
由於巨集只是在程式碼中做字串替代展開,所以,用巨集定義的函式,實際上並沒有減少程式碼的體積。另外,還有一些天然的缺陷,假設一個求平方的函式巨集
[cpp] view plain copy
#definesquare(x) (x*x)
voidf(double d, int i)
{
square(d); //OK
square(i++); //糟糕, (i++*i++)
square(d+1); //更糟,(d+1*d+1)
}
縱然可以把引數加上括號來解決,#define square(x) ((x)*(x)),但i++被執行兩次這個問題還是無法解決。
C++中的替代方案,就是使用inline函式。
[cpp] view plain copy
inline int square(intvalue)
{
return value*value;
}
inline函式具有函式的性質,引數傳遞不管是傳值還是傳引用,都不會對引數進行重複計算;同時會對引數做型別檢查,保證程式碼的正確性;inline函式也是在程式碼中做程式碼展開,效率上並不比巨集遜色。
如果整型不滿足需求,還可以定義為模板:
[cpp] view plain copy
template
inline T square(T& value)
{
return value*value;
}
還有一種更離譜的函式定義形式:
[cpp] view plain copy
#defineNull_Check(p)\
if(p == NULL) return;
這種使用反斜槓定義的函式,更得注意,如果在反斜槓後多了個空格的話,有的編譯器會出現變異錯誤,而提示資訊嘛,你可能會困擾很久的。因為反斜槓會對空格這個字元做反義,就會在程式碼中的引入非法字元,人眼是很難發現這種錯誤的。
3.型別重定義
#defineDWORD unsigned int
這種型別重定義完全可以使用 typedef unsigned int DWORD 來替代。
4.條件編譯
[cpp] view plain copy
#ifdefSystemA
testA();
#else//SystemB
testB();
#endif
這種條件編譯巨集,一般在不同的產品或平臺使用同一套程式碼的情況,大量出現。定義了SystemA的時候,SystemB的程式碼是不編譯的,也就意味著你的程式碼沒有時刻處於編譯器的監控中。可以使用template技術來解決。
[cpp] view plain copy
constint SystemA = 1;
constint SystemB = 2;
template
void test()
{}
//定義不同的系統的特化版本
template<> void test(){ //SystemA的實現 }
template<> void test(){ //SystemB的實現 }
這樣,不同的系統使用自己的模板即可,別人的程式碼也會同時接受編譯器的檢查,不至於出現遺漏編譯錯誤的情況。
5.標頭檔案包含
[cpp] view plain copy
#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++中並沒有實現。
如果你沒有很好的駕馭巨集,那就敬而遠之吧。