Effective C++ 條款2:儘量以const,enum,inline替換#define
#define用來定義常量。
首先我們需要知道,#define會在預編譯的時候,以字串替換的形式被替換掉。假設我們#define AspectRatio= 1.635。如果AspectRatio在使用的過程中報錯,編譯器會直接提示1.635。如果這個巨集是其他檔案定義的,那麼對於使用者而言,他甚至都不知道1.635是個什麼東西。
下面還有兩種特殊情況
定義常量指標
常量指標通常需要用兩個const。即既限定指標指向物件無法改變,也限定無法通過指標改變物件值。因為如果用#define定義指標的話,通常定義的是一個常量地址。這個地址代表的一定是一個固定的東西,因此必須是指標常量。並且,我們當然也不希望通過地址值直接修改任何東西。所以也必須是常量指標。
下面是個例子。
const char* const author_name = "Scott Meyers";
這裡面定義的一個指標,就是指標常量。但通常我們用string而非char*
const std::string author_name("Scott Meyers");
class專屬常量
類中的常量,我們希望他只有一版,因此需要定義為靜態的。
class GamePlayer{
private:
static const int number_turns = 5;
int scores[number_turns];
}
但由於上面的這個常量是寫在類中的,因此只是一個宣告,而非定義。在取地址的時候,如果這個常量是內建的整數型,int,char,bool,就沒什麼問題。但是如果是別的類,或者有的其他編譯器不允許這樣,那就要在外面定義一下。
const int GamePlayer::number_turns = 5;
因為C++要求任何東西都要被定義才能使用。
enum hack
上述問題還有一個解決方法,就是enum hack。列舉型的變數可以當成一個int使用。而且列舉型可能比const更像#define。因為const變數可以取地址,但是enum和#define都無法取地址。
class GamePlayer {
private:
enum {num_turns = 5};
int scores[num_turn];
}
濫用巨集
這一塊我們討論一個小問題,就是把巨集定義成非常複雜的函式。這樣可能導致一些意外的結果。下面是個例子
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
int a = 5, b = 0;
CALL_WITH_MAX(++a, b)
由於巨集會直接替換,所以最後就成了
f((++a) > (b) ? (++a) : (b))
很顯然,我們只希望a自加一次,但是這裡卻加了兩次。
解決這個問題的方法是使用模板行內函數
template<typename T>
inline void callWithMax(const T& a, const T& b) {
f(a > b ? a : b)
}
#define 設計的通常都是些簡單的函式,因此完全可以將他們設計成行內函數。最大的好處是,模板行內函數是個函式,他可以放在class當中。這樣就可以為它設計訪問性,比如public和private。但是巨集定義是沒有類中許可權的。
結論
- 對於單純常量,用const替換
- 對於形似函式的巨集,最好用inline替換