1. 程式人生 > >條款03(三):儘可能使用const

條款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並不會產生風險。

最後:

1、將某些東西宣告為const可以幫助編譯器偵測出錯誤的用法。const可以被施加於任何作用域內的物件、函式引數、函式返回型別、成員函式本體。

2、編譯器會強制實施bitwise constness,但是你在編寫程式是應該使用“概念上的常量性(conceptual constness)”

3、當const和non-const成員函式有實質等價的實現時,令non-const版本去呼叫const版本可以避免程式碼重複。