條款03(三):儘可能使用const
條款03:儘可能使用const
Use const whenever possible
在const和non-const成員函式中避免重複
在上一章的介紹中,雖然mutable是一個解決辦法,但是它無法解決所有的const問題。例如,在TextBlock中的operator[]不僅僅是返回一個reference以指向某一個字元,它也擁有其他的功能:
執行邊界檢測、記錄訪問資訊、進行資料完整性的檢測等等。
這時,如果要將這些功能同時放到const和non-const的operator[]內,會造成大量的重複的程式碼:
class TextBlock {
public:
...
//const:
const char& operator[] (std::size_t position) const
{
... //執行邊界檢測
...//記錄訪問資訊
...//進行資料完整性的檢測
return text[position];
}
//non-const:
char& operator[] (std::size_t position)
{
...//執行邊界檢測
...//記錄訪問資訊
...//進行資料完整性的檢測
return text[position];
}
private:
std::string text;
};
當然,上面的程式碼可以用另外一個private的成員函式來代替並令const和non-const operator[]來進行呼叫,但是依然重複了一些程式碼:比如函式的呼叫、返回語句等等。
此時,正確的做法是實現operator[]的功能一次,並使用它兩次。即,必須令其中一個呼叫另一個。於是,我們所要做的是將常量性移除。
在例子中,const operator[]實現了non-const operator[]所要做的事,唯一的區別就是返回型別多了一個const資格修飾。
此時,如果將返回值的const轉除(也就是去除其const屬性),是安全的,因為無論是誰呼叫non-const operator[],都一定首先有一個non-const物件,否則就不能呼叫non-const函式。
因此,令non-從上圖 operator[] 呼叫其const兄弟是一個避免程式碼重複的安全做法——即使這個過程需要一個轉型動作。
class TextBlock {
public:
...
//const:和原先一樣
const char& operator[] (std::size_t position) const
{
...
...
...
return text[position];
}
//non-const:發生區別,直接呼叫了const op[]
char& operator[] (std::size_t position)
{
return //直接return
const_cast<char&>( //將op[]返回值的const移除
static_cast<const TextBlock&>(*this) //為*this加上const
[position] //呼叫const op[]
);
}
...
}
在上面的程式碼中,發生了兩次轉型。
首先,我們打算讓non-const operator[]呼叫其兄弟const,但是如果直接的去呼叫operator[],只會遞迴的呼叫自己,陷入呼叫的死迴圈中。為了避免這種遞迴的死迴圈,我們必須要指出呼叫的是const operator[],但是C++中又沒有對應的方法來實現。
因此,這裡將*this從其原始型別TextBlock& 轉換為const TextBlock&,即使用轉型操作來加上const。新增const的這一次轉型強迫進行了一次安全轉型(將non-const物件轉為const物件)。
- 第一次,用來為*this新增 const,使得接下來呼叫operator[]是可以條用const的版本,使用static_cast。
- 第二次,則是從const operator[]的返回值中移除const,利用const_cast來完成。
由此,運用const成員函式實現了non-const孿生兄弟得以實現,避免了程式碼的重複,這種方法是值得學習和理解的。
但是,值得注意的是,反向做法——令const版本呼叫non-const版本以避免重複——是不應該的!。因為,const成員函式承諾了絕不改變其物件的邏輯狀態(logical state),但non-const成員函式卻沒有這樣的承諾。
如果const函式內呼叫了non-const,就會出現這樣的風險:曾經承諾不改動的那個物件被改動了!
而原本的做法——non-const的版本去呼叫const版本,才是安全的,因為non-const成員函式本身就可以對其物件做任何動作,因此呼叫const並不會產生風險。
最後: