const 與 mutable


如果在建立 string 實例時指定 const,那表示不能變動該實例的狀態,如果試圖改變該實例狀態,或者呼叫了會變動實例狀態的方法,編譯時會發生錯誤:

const string text = "Justin";
text.append(" Lin") // error: no matching function

const 修飾表示不能變動實例狀態,這並不是自動發生的事情,如果呼叫的方法沒有被 const 修飾,就不能通過編譯,例如,若如下使用〈定義類別〉中的 Account,雖然 to_string 並沒有變動實例狀態,也不能通過編譯:

#include <iostream> 
#include "account.h"
using namespace std;

int main() {
    const Account acct = {"123-456-789", "Justin Lin", 1000};
    cout << acct.to_string() << endl; //  error: passing 'const Account' as 'this' argument discards qualifiers
}

如果要通過編譯的話,to_string 必須加上 const 限定:

account.h

#include <string>
using namespace std; 

class Account { 
private:
    string id;  
    string name; 
    double balance;

public: 
    Account(string id, string name, double balance);
    void deposit(double amount);
    void withdraw(double amount);
    string to_string() const;
};

account.cpp

...略

string Account::to_string() const {
    return string("Account(") + 
           this->id + ", " +
           this->name + ", " +
           std::to_string(this->balance) + ")";
}

...略

當方法被加上 const 限定後,方法就不能有改變值域的動作,有了這個保證,方才的 to_string 呼叫才能通過編譯。

另一個類似的問題是:

#include <iostream> 
#include "account.h"
#include <string>
using namespace std;

class Foo {
public:
    Foo& doSome() {
        return *this;
    }
    Foo& doOther() {
        return *this;
    }
};

int main() {
    const Foo foo;
    foo.doSome().doOther();
}

這個程式在 Foo foo 前,若沒有加上 const 的話,是可以通過編譯的,你可能會想,那就在 doSomedoOther 函式本體前,也加上 const 就可以了吧!可惜…加上了還是不能通過編譯!

const 的要求很嚴格,不僅要求方法不能變動實例狀態,如果以參考傳回型值域,或者是如上以參考傳回實例本身,也會要求傳回值的狀態不得改變,必須得如下才能通過編譯:

#include <iostream> 
#include "account.h"
#include <string>
using namespace std;

class Foo {
public:
    const Foo& doSome() const {
        return *this;
    }
    const Foo& doOther() const {
        return *this;
    }
};

int main() {
    Foo foo;
    foo.doSome().doOther();
}

你可能會想在被限定為 const 的方法中,可以改變某些值域,因為這些值域的改變,從使用者來看,並不代表實例狀態的改變。

這是什麼意思呢?例如,若有個 hashCode 方法,可以計算出物件的雜湊值,實際上可以在首次呼叫該方法計算出雜湊值之後,使用值域將雜湊儲存下來,物件的 hashCode 結果從使用者來看並沒有變,若是有這類需求,值域在宣告時,可以加上 mutable

class Foo {
    mutable int hash = 0;

public:
    int hashCode() const {
        if(hash == 0) {
            // 計算雜湊值 v
            this->hash = v;
        }

        return this->hash;
    }
};