Item 03:Use const whenever possible
如果關鍵字const出現在星號左邊,表示被指物是常量;如果出現在星號右邊,表示指針自身是常量;如果出現在星號兩邊,表示被植物和指針兩者都是常量。
STL迭代器以指針爲根據塑造出來,所以迭代器的作用就像個T*指針。聲明迭代器爲const就像聲明指針爲const一樣,如:
std::vector<int>vec;
const std::vector<int>::iterator iter = vec.begin();//iter的作用就像一個T*const
std::vector<int>::const iterator cIter = vec.begin();//cIter的作用就像一個const T*
令函數返回一個常量值,往往可以降低因客戶錯誤而造成的意外,而又不至於放棄安全性和高效性。
舉個例子,考慮有理數operator*的聲明式:
class Rational{......};
class Rational{......};
const Rational operator* (const Rational & lhs,const Rational & rhs);
如果不返回一個const對象客戶就可能實現這樣的暴行:
Rational a,b,c;
...
(a*b)=c; //在a*b的結果上調用operator =
很多程序員可能在無意中這麼做,只因爲單純的打字錯誤:
if(a*b=c)...//其實只是想做一個比較動作!
一個“良好的用戶自定義類型”的特徵是它們避免無端地與內置類型不兼容,因此允許對兩值乘積做賦值動作也就沒什麼意思了。將operator*的回傳值聲明爲const可以預防那個“沒意思的賦值動作”。
const 成員函數
將const實施於成員函數的目的,是爲了確認該成員函數可以作用於const對象。
許多人漠視一件事實:兩個成員函數如果只是常量性不同,可以被重載,考慮如下一段代碼:
class TextBlock
{
public:
...
const char & operator[](std::size_t position) const//operator[]for const 對象
{
return text[position];
}
char & operator[](std::size_t position)//operator[]for non-const對象
{
return text[position];
}
private:
std::string text;
}
TextBlock的operator[]可以被這麼用:TextBlock tb("Hello");
std::cout<<tb[0];//調用non-const TextBlock::operator[]
const TextBlock ctb("World");//調用const TextBlock::operator[]
std::cout<<ctb[0];
void print (const TextBlock &ctb)
{
std::cout<<ctb[0];
...
}
只要重載operator[]並對不同的版本給予不同的處理返回類型,就可以令const和non-const獲得不同的處理。請注意,non const operator[]的返回值類型是一個reference to char,不是char。否則下面的句子就無法通過編譯:
tb[0]='x';
這是因爲,如果函數的返回值是個內置類型,那麼改動函數返回值從來就不合法。縱使合法,C++以by value返回對象這一事實意味着被改動的其實是tb.text[0]的一個副本,不是其自身,那不是你想要的行爲。
成員函數爲const意味着什麼?這有兩個流行的概念:bitwise constness 和 logical constness
bitwise constness陣營的人相信,成員函數只有在不更改對象之任何成員變量時纔可以說是const。然而不幸的是很多成員函數雖然不十分具備const性質卻能通過bitwise測試。具體地說,一個更改了“指針所指物"的成員函數雖然不能算是const,但如果只有指針(而非其所指物)隸屬於對象,那麼稱此函數爲bitwise constness 不會引發編譯器異議。如下:
class CTextBlock
{
public:
...
char& operator[](std:size_t position) const//bitwise constness聲明,但其實並不妥當
{
return pText[position];
}
private:
char* pText;
};
這個class不恰當地將其operator[]聲明爲const成員函數,而函數卻返回一個reference指向對象的內部值。由於operator[]的實現並不更改pText,於是編譯器很開心地認爲它是bitwise constness,但是看看它會發生什麼事:
const CTextBlock cctb("Hello");
char* pc=&cctb[0];//調用const operator[]取得一個指針,指向cctb的數據
//我認爲這裏應該是char*pc =cctb[0],歡迎大家一起討論
*pc='J';//cctb 現在有了"Jello"這樣的內容
這就是所謂的logical constness。這一派的擁護者主張,一個const成員函數可以修改它所處理對象內的某些bit,但只有在客戶端偵測不出的情況下才得如此。例如你的CTextBlock class可能高速緩存文本區塊的長度以便應付詢問:
class CTextBlock
{
public:
...
std::size_t length() const;
private:
char *pText;
std::size_t textLength; //最近一次計算的文本區塊長度
bool lengthIsValid; //目前的長度是否有效
};
std::size_t CTextBlock::length() const
{
if(!lengthValid)
{
textLength = std::strlen(pText);
lengthIsValid = true; //錯誤!在const成員函數內不能賦值給textLength和lengthIsValid
}
return textLength;
}
length修改了textLength和lengthIsValid,這兩筆數據被修改對const CTextBlock來說可以接受,但是編譯器不同意,它堅持bitwise constness,怎麼辦呢?解決方法很簡單:利用C++寫一個與const相關的擺動場:mutable。mutable釋放掉non-static成員變量的bitwise constness約束:
class CTextBlock
{
public:
...
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength;
mutable bool lengthIsValid; //這些成員變量可能總是會被修改,即使是在const成員函數內
};
std::size_t CTextBlock::length() const
{
if(!lengthValid)
{
textLength = std::strlen(pText);//現在,可以這樣
lengthIsValid = true; //也可以這樣
}
return textLength;
}
在const和non-const成員函數中避免重複
對於”bitwise-constness 非我所欲”的問題,mutable是個解決辦法,但它不能解決所有的const相關難題。假設TextBlock內的operator[]不單只是返回一個reference指向某字符,也執行邊界檢驗、志記訪問信息、甚至可能進行數據完善性檢驗。如下:
class TextBlock
{
public:
...
const char& operator[](std::size_t position) const
{
... //邊界檢驗
... //志記數據訪問
... //檢驗數據完整性
return text[position];
}
char& operator[](std::size_t position)
{
... //邊界檢驗
... //志記數據訪問
... //檢驗數據完整性
return text[position];
}
private:
std::string text;
};
你能說出其中發生的代碼重複以及伴隨的編譯時間、維護、代碼膨脹等令人頭疼的問題嗎?你真正應該做的是實現operator[]的機能一次並實現它兩次。這促使我們將常量性移除。如下:
class TextBlock
{
public:
...
const char& operator[](std::size_t position) const
{
... //邊界檢驗
... //志記數據訪問
... //檢驗數據完整性
return text[position];
}
char& operator[](std::size_t opsition)
{
return
const_cast<char&>(//將op[]返回值的const移除
static_cast<const TextBlock&>(*this)//爲*this加上const
[position]);//調用const op[]
}
};
這份代碼有兩個轉型動作。我們打算讓non-const operator []調用其const兄弟,但non-const operator[]內部若只是單純調用operator[],會遞歸調用自己。爲了避免無窮遞歸,我們必須明確指出調用的是const operator[]。因此這裏將*this從其原始類型TextBlock&轉換爲const TextBlock&,這使接下來調用operator[]時得意調用const版本。第二次則是從const operator []的返回值中移除const。
至於反向做法——令const版本調用non-const版本以避免重複——那並不是你應該做的事。因爲const成員函數承諾絕不改變其對象的邏輯狀態而non-const成員函數卻沒有這般承諾。如果在const函數內調用non-const函數,就是冒了這樣的風險:你曾經承諾不改動的那個對象唄改動了。
請記住:
1.將某些東西聲明爲const可幫助編譯器偵測出錯誤用法。const可被施加於任何作用域內的對象、函數參數、函數返回類型、成員函數本體。
2.編譯器強制實施bitwise constness,但你編寫程序是應該使用“概念上的常量性”。
3.當const和non-const成員函數有着實質等價的實現時,令non-const版本調用const版本可避免代碼重複。