Effective C++學記之03 儘可能使用const

1 const和指針的關係

對於我這個c++初學者來說,指針搭配常量讓人有點小頭疼。在描述一些概念比如“指向const的const指針”更讓人大腦一時轉不過來。

看書中的例子:

char greeting[] = "HELLO"
char* p = greeting;                        //non-const pointer,non-const data
char* const p = greeting;             //const pointer,non-const data
const char* p = greeting;             //non-const pointer,const data
const char* const p = greeting;  //const pointer,const data

記住書中總結的三句話可以理清const和指針的關係。

const 出現在*左邊,表示被指物是常量    const char* p 等價於 char const *p
const 出現在*右邊,表示指針自身是常量
const出現在*兩邊,被指物和指針都是常量

下面通過迭代器的例子來說明const和指針的關係:

std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin();  //等價於聲明一個T* const指針 指針自身不可改變,被指物可以改動。
*iter = 10;  //OK 改變被指物
++iter; //error iter是const

爲了改變指針,可以採用const_iterator,如下:

const std::vector<int>::const_iterator cIter = vec.begin(); //等價於const T*
*cIter = 10 ;//error! 被指物是const
++cIter;   //OK 改變指針自身


2  聲明爲const可以幫助編譯器偵測出錯誤用法。const 可以被加到任何作用於內的對象,函數參數,返回值,成員函數本體。
看下面的例子:

const Rational operator*(const Rational& l,const Rational& r) //重載了operator* 的操作
調用時:
Rational a,b,c
if((a*b)= c)  //爲什麼會有這種寫法?其實本意是(a*b)== c)好不好! 使用const可以避免(a*b)被重新複製。


3 兩個成員函數如果是常量性不同,可以被重載。

例:
class TextBlock{
    public:
    const char& operator[](std::size_t pos) const //①const object
    {
        return text[pos];
    }
    char& operator[](std::size_t pos)  //②non-const object
    {
        return text[pos];
    }
}
使用時:
TextBlock tb("hello"); // 匹配②non-const函數
cout << tb[0];

void print(const TextBlock& ctb) // 匹配①const函數
{
    cout << ctb[0];
}


4 編譯器強制實施bitwise constness,但是在編碼時應使用概念上的常量性(conceptual constness)。

bitwise constness陣營的人相信:成員函數只有在不更改對象的任何成員變量(static除外)時纔可以說是const。
也就是說const成員函數不可以更改任何non-const成員變量。

不幸的是:如果一個更改了“指向物”的成員函數不能算是bitwise,但是如果其指針屬於對象,那麼此函數爲bitwise可以被編譯器編譯。

看下面的例子:
class CTextBlock{
public:
    char& operator[](std:size_t position) const
    {return pText[position];} 
private:
    char* pText;  //指針屬於對象的成員變量
};
該函數不適當地聲明瞭一個const函數,而函數返回一個引用指向內部值。    由於operator[]實現代碼並不更改pText,因此編譯器認爲是bitwise const。
但是以下操作終究還是可以改變它的值:
const cTextBlock cctb("hello");
char* pc = &cctb[0];
*pc ='J';

以上的情況編譯器不會報錯,但是終究還是改變了值。於是導出了logical constness,他們認爲一個const成員函數可以修改它所處理的對象內的某些bits,但只在客戶端偵測不出的情況下才得如此。

下面這個例子CTextBlock class實現高速緩存文本區塊的長度以便應付詢問:
class CTextBlock{
public:
    std::size_t lenth() const;
private:
    char* pText;
    std::size_t textLenth; //最後一次計算的文本區塊長度。non-const都可能被修改
    bool lenthIsValid; //目前的長度是否有效。non-const都可能被修改
};
std::size_t CtextBlock::lenth() const;
{
    if(!lenthIsValid){
        textLenth = std::strlen(pText); //error! 在const成員函數中不能賦值給textLenth和lengthIsValid,解決方法:mutable
        lenthIsValid = true;//error!
    }
    return textLenth;
}

上面的解決方法可以使用mutable(可變的),它能放掉non-static成員變量的bitwise constness約束。
class CTextBlock{
public:
    std::size_t lenth() const;
private:
    char* pText;
    mutabe std::size_t textLenth; //最後一次計算的文本區塊長度。non-const都可能被修改
    mutabe bool lenthIsValid; //目前的長度是否有效。non-const都可能被修改
};


5 當const和non-const成員函數有着等值等價的實現時,令non-const版本調用const版本可以避免代碼重複。

令non-const operator[]調用其const兄弟可以避免代碼重複的安全做法如下:
class TextBlock{
public:
    const char& operator[](std::size_t pos) const
    {
        。。。//各種等價check
        return text[pos];
    }
    char& operator[](std::size_t pos)
    {
        return   //調用const將op[],將返回值的const轉除????
            const_cast<char&>(
                static_case<const TextBlock&>(*this)[pos]
            );
    }
    
}

說明:兩次轉型
(1)<const TextBlock&>(*this) 爲了避免調用自己無窮遞歸,將*this從原始類型TextBlock& 轉爲const TextBlock&  。使用static_case進行強迫安全轉型。
(2)從const operator[]的返回值中移除const

注:可以在non-const中調用const,但是反之從const中調用non-const存在不安全性,不提倡。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章