effective C++筆記——儘量替換#define
文章目錄
巨集替換的工作是由前處理器來做的,它不會被視為語言的一部分,因此可能發生的情況是:
定義瞭如下巨集提換語句:#define RATIO 1.653
也許在編譯器處理原始碼前這句話就被前處理器移走了,該記號名稱可能沒有進入記號表(symbol table)內。於是當運用此常量但獲得一個編譯錯誤時,該資訊可能提到1.653而不是RATIO,這可能對除錯產生很大的干擾。
解決之道是使用常量表達式來替換上述巨集語句:
const double Ratio = 1.653;
作為語言常量,肯定會被編譯器所看到,因此肯定會進入到記號表內。
class專屬常量
#define並不重視作用域,因此無法利用#define來建立一個class專屬常量,也不能提供任何封裝性,即沒有private #define這樣的形式,而const成員變數當然是可以封裝的:
class Cost{
private:
static const double FudgeFactor; //宣告
};
const double Cost::FudgeFactor = 1.35; //定義
有的編譯器不支援在class內部定義static的變數,但是有時在編譯程式的時候需要給class一個常量值,例如在class中宣告陣列,這時可以使用列舉型別來充當int使用:
class GamePlayer{
private:
enum {NumTurns = 5};
int score[NumTurns];
};
使用inline替代#define
另一個常見的#define的誤用是以它實現巨集,巨集看起來像是函式,但不會招致函式呼叫產生的額外開銷,例如:
//以a、b間的較大值呼叫函式f
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))
這種樣式的巨集,需要確保為每個實參加上小括號,否則可能帶來麻煩,但是即使這麼做了,也可能發生以下這種情況:
int a = 5,b = 0; CALL_WITH_MAX(++a,b); //a將被累加兩次 CALL_WITH_MAX(++a,b+10); //a將被累加一次
以上a的累加次數會受到b的值影響,這種不確定性就是麻煩的根源。
用template inline函式將帶來巨集可以達到的效率以及一般函式的所有可預料行為和型別安全性:
template <typename T>
inline void callWithMax(const T& a,const T& b){
f(a > b ? a : b);
}
這裡不需要在函式本體中為引數加上括號,也不用擔心引數被計算多次,此外這種寫法將callWithMax表明為一個真正的函式,它將遵守作用域和訪問規則。
有了const、enum和inline,對前處理器的需求降低了,但並非完全消除,只是對於單純常量,最好以const物件或者enums替換#define;對於像函式一樣的巨集,最好使用inline來代替。
儘可能使用const
const允許指定一個語義約束(“一個不該被改動”的物件),編譯器會強制執行這項約束。
const使用範圍也很大,可以用在classes外部的global或是namespace作用域中修飾常量,也可以修飾檔案、函式或是區塊作用域中被宣告為static的物件,還可以修飾classes內部的static和非static的成員變數。
對於指標來說,如果關鍵字出現在星號左邊,表示被指向的是常量;如果出現在星號右邊,表示指標本身是常量;如果出現在星號兩邊,表示兩者都是常量。
關於STL中的迭代器來說,宣告迭代器為const表示的是這個迭代器是常量,即不能指向別的東西,想宣告迭代器所指向的東西為常量應該使用const_iterator:
vector<int> v;
...
const vector<int>::iterator iter = v.begin();
*iter = 10; //正確
iter++; //錯誤,迭代器是const的
const vector<int>::const_iterator iter2 = v.begin();
*iter2 = 10; //錯誤,所指向的是const
iter2++; //正確
const還可以子啊面對函式宣告時使用,在一個函式宣告式內,const可以和函式返回值、各引數、函式自身(如果是成員函式的話)產生關聯。
令函式返回一個常量值可以降低意外操作帶來的影響比如在if判斷中做比較的時候少寫一個等號,就變成賦值操作了。
const成員函式
const作用於成員函式的目的是為卻確認該成員函式可作用於const物件身上。其重要性基於兩點:
1.使得class介面比較容易理解。得知哪個函式可以改動物件而哪些不可以很重要;
2.使得“操作const物件”成為可能。
需要注意的是在const成員函式中是不能使用賦值操作給成員變數進行賦值的,如果想要給成員變數賦值,可以將成員變數宣告為mutable的:
class CTextBlock{
public:
std::size_t length() const;
private:
char* pText;
std::size_t textLength;
mutable bool lengthIsValid;
};
std::size_t CTextBlock::length() const{
if(!lengthIsValid){
textLength = std::strlen(pText); //錯誤
lengthIsValid = true; //正確
}
return textLength;
}
另外一種令人頭疼的問題是在一個cosnt成員函式中所需要做的操作較多,這樣的話,相當於兩種版本(const和非const)會擁有大量重複的程式碼,這伴隨而來的是編譯時間、維護、程式碼膨脹的問題。這促使我們將常量性移除。
型別轉換其實是不好的想法,但是程式碼重複也不是什麼令人愉快的經驗。以下是兩種版本的避免程式碼重複的安全做法–即使需要轉型:
class TextBlock{
public:
...
const char& operator[](std::size_t postion) const{
...
...
... //很多操作
return text[position];
}
char& operator[](std::size_t postion) {
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
...
};
程式碼中有兩個轉型操作,傳入的引數轉型為const,返回的值轉型為非const。值得注意的是使用const版本呼叫一個非const版本是不應該做的,因為const函式是承諾絕不改變物件的邏輯狀態的,而非const函式沒有這樣的承諾,如果在const函式中呼叫了非const函式,就是冒了這樣的風險,反向呼叫(在非const函式中呼叫const函式)才是安全的。