目錄安全
關鍵字const能夠用於如下對象。函數
STL迭代器以指針爲根據塑模出來,因此STL迭代器的做用就像個T *指針。post
std::vector<int> vec; const std::vector<int>::iterator iter = vec.begin(); //iter至關於T *const指針 *iter = 10; //沒問題 ++iter; //錯誤,iter不可修改 std::vector<int>::const_iterator const_iter = vec.begin(); //const_iter至關於const T *指針 *const_iter = 10; //錯誤,*const_iter不可修改 ++const_iter; //沒問題
const最具威力的用法是面對函數聲明時的應用。在一個函數聲明中,const能夠和函數參數、函數返回值、函數自身(若是是成員函數)產生關聯。
const用於函數參數只需記住一條原則:除非函數體中須要改動參數,不然就將它們聲明爲const。
const用於函數返回值,每每能夠下降因使用錯誤而形成的意外,同時又不至於放棄安全性和高效性,舉個例子,看下面有理數類operator *的聲明。this
class Rational { ... }; const Rational operator * (const Rational &lhs, const Rational &rhs);
Rational a, b, c; (a * b) = c; //有意錯誤,對兩個數的乘積進行賦值,就比如1 = 2同樣 if (a * b = c) //無心錯誤,將==漏寫爲=
將const用於成員函數的目的,是爲了確認該成員函數可做用於const對象身上。const成員函數之因此重要,基於兩個理由。編碼
第2條對於編寫高效代碼是個關鍵,由於如條款20所述,改善C++程序效率的一個根本方法是以const引用的方式傳遞對象。
const引用的多是const對象,而const對象只能調用const成員函數。
因此此技術可行的前提是,有const成員函數可用來處理取得的const對象;不然,就算能將const對象傳進來,也沒有辦法去處理它。spa
C++有一個重要特性:兩個成員函數若是隻是常量性不一樣,則能夠構成重載,即便它們的參數類型、參數個數、參數順序都徹底一致。
基於這個特性,再結合上面提到的高效編碼技巧,就能夠得出以下所示的接口設計。設計
class TextBlock { private: std::string text; public: //用於const對象,因爲const對象內容不容許修改,所以返回值也加了const const char &operator [] (std::size_t postion) const { return text[postion]; } //用於non-const對象 char &operator [] (std::size_t postion) { return text[postion]; } }; void print(const TextBlock &text) { std::cout << text[0]; } TextBlock text; const TextBlock const_text; print(text); //調用char &operator [] () print(const_text); //調用const char &operator [] () const
C++編譯器要求const成員函數不能更改對象內的任何non-static成員變量,簡單地說就是const成員函數中不能出現對non-static成員變量的賦值操做。
這種要求實質上是不能更改對象內的任何一個bit,所以叫作bitwise constness。
不幸的是,許多const成員函數雖然不徹底具有const性質,但卻能經過C++編譯器的bitwise檢驗,更具體地說,就是:指針
class TextBlock { private: char *pText; public: char &operator [] (std::size_t postion) const { return pText[postion]; } }; const TextBlock text("Hello"); //聲明一個const對象 char *pc = &text[0]; //調用const char &operator []取得一個指針,指向text的數據 *pc = 'J'; //經過pc指針將text的數據改成了"Jello"
上面這個class將operator []聲明爲const成員函數,但卻返回了一個reference指向對象內部數據,這種作法是錯誤的,條款28對此有深入討論,咱們暫時先忽略它。
從編譯器bitwise constness的角度看,上述代碼不存在任何問題,但你終究仍是改變了const對象的值,這種狀況導出所謂的logical constness。日誌
logical constness指的是,const成員函數能夠修改它所處理對象內的某些bits,但前提是用戶察覺不到這種修改。
要想在const成員函數中修改non-static成員變量,須要對這些成員變量使用mutable
關鍵字,mutable能夠去除non-static成員變量的bitwise constness約束。code
class CTextBlock { private: char *pText; mutable std::size_t textLength; //最近一次計算的文本長度 mutable bool lengthIsValid; //目前的長度是否有效 public: std::size_t length() const; }; std::size_t CTextBlock::length() const { if (!lengthIsValid) { textLength = std::strlen(pText); lengthIsValid = true; } return textLength; }
length()的實現固然不是bitwise constness,由於textLength和lengthIsValid均可能被修改,但這兩個成員變量被修改對於const CTextBlock對象是能夠接受的。
如今咱們對class TextBlock作一些修改,假設operator []不單只是返回一個reference指向某字符,還執行邊界檢查、日誌數據訪問、數據完整性檢驗等工做。
class TextBlock { private: std::string text; public: //用於const對象,因爲const對象內容不容許修改,所以返回值也加了const const char &operator [] (std::size_t postion) const { ... //邊界檢查 ... //日誌數據訪問 ... //數據完整性檢驗 return text[postion]; } //用於non-const對象 char &operator [] (std::size_t postion) { ... //邊界檢查 ... //日誌數據訪問 ... //數據完整性檢驗 return text[postion]; } };
operator[]的const和non-const版本中的代碼重複,可能會隨着編譯時間、持續維護、代碼膨脹等因素而成爲使人頭痛的問題。
將重複代碼封裝到一個private函數中,並分別在兩個函數中調用它,不失爲一個解決該問題的好辦法,但依然存在代碼重複,如函數調用、return語句。
真正最好的辦法是:先實現operator []的const版本,而後在non-const版本中調用它。以下示例代碼所示,這種方法有兩個技術要點。
class TextBlock { private: std::string text; public: //用於const對象,因爲const對象內容不容許修改,所以返回值也加了const const char &operator [] (std::size_t postion) const { ... //邊界檢查 ... //日誌數據訪問 ... //數據完整性檢驗 return text[postion]; } //用於non-const對象 char &operator [] (std::size_t postion) { const TextBlock &const_this = static_cast<const TextBlock &>(*this); //將自身從TextBlock &轉換爲const TextBlock & return const_cast<char &>(const_this[postion]); //調用const版本的operator [],並去除返回值中的const屬性,而後返回 } };
注意,千萬不要令const版本調用ono-const版原本避免代碼重複,由於const版本調用non-const版本的惟一方法是去除自身的const屬性,這絕對不是個好事情。