天天看點

讀書筆記 effective c++ Item 2 盡量使用const,枚舉(enums),内聯(inlines),不要使用宏定義(define)

這個條目叫做,盡量使用編譯器而不要使用預處理器更好。#define并沒有當作語言本身的一部分。

例如下面的例子:

1 #define ASPECT_RATIO 1.653      

符号名稱永遠不會被編譯器看到。它可能在源碼到達編譯器之前被預處理器移除。ASPECT_RATIO 最終不會進入符号表,如果因為這個常量的使用而導緻編譯錯誤,會使你非常迷惑,因為錯誤資訊會指向1.653而不是ASPECT_RATIO。如果ASPECT_RATIO被定義在一個不是你自己寫的頭檔案中,你會不知道1.,653來自哪裡。,

解決方法是将宏替換成常量:

1 Const double AspectRatio = 1.653      

作為一個語言常量,AspectRatio能夠被編譯器看到,編譯器也肯定能進入到AspectRatio的符号表中。此外,對于浮點常量來說,使用常量比使用宏定義會産生更少的代碼。因為預處理器會盲目的将宏定義名稱ASPECT_RATIO替換成1.653,這會造成在目标碼中1.653的多份拷貝,而常量的使用最多産生一份拷貝。

當用常量替換宏定義的時候,有兩種特殊情況值得提一下:

第一種是定義常量指針,因為常量定義會被放到頭檔案中,很多檔案會包含這個頭檔案,将指針聲明成常量,同時将指針指向的内容也聲明成常量。為了在頭檔案中定義一個基于char*的字元串,必須寫const兩次:

1 Const char* const authorname = “Scott”      

在這裡有必要提醒一下使用string對象要優于基于char*的字元串,是以将authorname定義成如下方式更好:

1 const std::string authorname(“Scott”);      

第二種特殊的情況是關于類中指定的常量。為了将常量的作用域限制在類中,必須将其聲明成一個成員,為了保證至多隻有一份常量的拷貝,你必須将其聲明成static 成員:

1 Class GamePlayer{
2 
3 Private:
4 
5 Static const int NumTurns = 5;
6 
7 Int scores[NumTurns];
8 
9 }      

上面看到的是NumTurns的聲明而非定義,c++需要你為你所使用的任何東西(anything)提供一份定義,但是類專屬的靜态整型常量(intergers,chars,bools)是一個例外,隻要你不使用他們的位址,你可以聲明并且使用他們而不用提供一個定義。如果你需要取得類專屬常量的位址或者你所使用的編譯器錯誤的堅持類專屬常量需要一個定義(即使不需要擷取位址),你需要提供一個單獨的定義:

1 Const int GamePlayer::NumTurns;      

你需要把定義放到實作檔案而不是頭檔案中。因為類專屬對象的初始值是在聲明時提供的,不允許在定義的時候對其進行初始化。

順便說一句,不可以使用宏定義為類定義專屬常量,因為宏定義沒有作用域。一旦一個宏定義被定義,它就在餘下的編譯中有效(隻要它沒有被undefed)。這意味者宏定義不能當作類專屬常量,它們也不能用來提供任何類型的封裝,例如,沒有私有的#define.

舊的編譯器也許不會接受上面的文法,因為在過去,為靜态類成員在聲明處提供初始值是非法的,此外,隻允許整型和常量進行類内部的初始化。一旦上面的文法不能用了,你需要把初始化值放在定義處。

1 Class costEstimate{
2 
3 Private:
4 
5 Static const double FudgeFactor;
6 
7 }
8 
9 Const double costEstimate::FudgeFactor=1.35;      

這是你任何時候需要做的,唯一的例外是在類編譯過程中你需要一個類常量值,例如在類中聲明一個數組,在編譯過程中需要知道數組的大小。這時候在類内部為靜态整型常量值指定初始值是被禁止的(這是不正确的),補償的做法是使用”enum hack”.這種技術利用了一個事實:枚舉類型的值可被用在需要整型值的地方,是以可以如下定義:

1 Class Gameplayer
 2 
 3 {
 4 
 5 Private:
 6 
 7 Enum{NumTurns=5};
 8 
 9 Int scores[NumTurns];
10 }      

Enum hack技術值得被了解,有以下幾個原因:

第一,  enum hack的行為在一些情況下更像宏定義而不是const,有時候這也是你所需要的。例如:取得const的位址是合法的,但擷取枚舉的位址是不合法的,同樣的,擷取宏定義的位址是不合法的。如果你不想讓其他人擷取指向整型常量的指針或者引用,枚舉是進行這種限制的一個好的方法。同樣,雖然好的編譯器不會為整型常量配置設定額外的空間,一個草率的編譯器可能會這麼做,這是你不願意看到的。像宏定義一樣,枚舉永遠不會産生這樣的不必要的記憶體配置設定。

第二,  enum hack是很實用的技術,很多代碼都會使用到它,是以當你看到它你應給能夠識别出來。事實上,enum hack是模闆元程式設計的基本技術。

回到預處理器,#define的另外一個用法是實作一個看上去像函數的宏,但對其調用不會招緻額外開銷。下面是一個取最大值的例子:

1 #define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))      

這樣的宏有許多缺點,想想都頭疼。

當你實作這類宏時,你必須記住對宏定義體中的所有參數都要加上括号,否則别人在表達式中調用宏的時候會遇到 麻煩。但是即使你那麼做了,你仍然會遇到奇怪的事情

1 int a = 5, b = 0;
2 CALL_WITH_MAX(++a, b); // a is incremented twice
3 CALL_WITH_MAX(++a, b+10); // a is incremented once      

這裡a加一的次數取決于a和一個多大的數進行比較。

幸運的是,你可以不必忍受這麼無聊的事情。你可以通過定義一個内聯函數模闆來獲得宏定義函數所有的效率并且可預知函數的所有行為,函數也是類型安全的。

1 template<typename T>                                                // because we don’t
2 inline void callWithMax(const T& a, const T& b) // know what T is, we
3 
4 {
5 
6   F(a>b?a:b);
7 }      

這個宏定義會産生一個函數族,每個函數将相同類型兩個對象作為參數,其中較大的調用f,不必給函數體内部的參數加括号,也不必擔心參數會被求值多次。并且因為callWithMax是一個函數,它遵循作用域和通路規則。比如,你可以寫出一個類的私有内聯函數。宏定義卻不能夠做到。

鑒于consts,enums和inlines的實用性,你可以減少預處理器的使用,但是它并沒有被清除,#inlcude仍然是必要的,#ifdef和#ifndef在編譯控制上仍然發揮重要作用,還沒有到讓預處理器退休的時候,但是你絕對可以給它放一個長長的假期。

作者:

HarlanC

部落格位址:

http://www.cnblogs.com/harlanc/

個人部落格:

http://www.harlancn.me/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出,

原文連結

如果覺的部落客寫的可以,收到您的贊會是很大的動力,如果您覺的不好,您可以投反對票,但麻煩您留言寫下問題在哪裡,這樣才能共同進步。謝謝!

繼續閱讀