1. 程式人生 > 實用技巧 >const 成員函式與基於 const 的過載

const 成員函式與基於 const 的過載

const 成員函式

const 成員函式的作用是允許 const 物件使用成員函式。

以下內容來自《C++ Primer 5th》:

預設情況下 this 指標的型別是指向類型別非常量版本的常量指標。具體來說就是:StrBlob* const(StrBlob 是一個類),這也就意味著,this 指標無法指向一個常量 StrBlob 物件,也就無法在常量 StrBlob 物件上使用普通的成員函式。

所以我們想讓 this 變為指向常量的常量指標,C++ 中的做法是在普通成員函式的引數列表後面加上 const 關鍵字。這樣的成員函式叫做常量成員函式

class StrBlob{
    friend ostream& operator << (ostream&, const StrBlob&);
    friend istream& operator >> (istream&, StrBlob&);
public:
    typedef vector<string>::size_type size_type;
    StrBlob() :data() {}
    StrBlob(initializer_list<string> il) :data(make_shared<vector<string>>(il)) {}
    void push_back(const string& s) { data->push_back(s); }
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void pop_back() const;
private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string& msg) const;
};

基於 const 的過載

簡單來說,基於 const 過載背後的思想大致是:讓非常量物件使用非常量成員函式,讓常量物件使用常量成員函式。

這個過載根據呼叫物件是否為常量,呼叫相應的成員函式。

下面的示例程式碼來自《C++ Primer 5th》:

class Screen{
public:
    Screen &display(std::ostream& os) { do_display(os); return *this; }
    const Screen &display(std::ostream& os) const {
        do_display(os);
        return *this;
    }
private:
    void do_display(std::ostream &os) const { os << contents; }
};

如果是一個常量物件,display 會呼叫 const Screen &display(std::ostream& os) const
如果是一個非常量物件,display 會呼叫 Screen &display(std::ostream& os)

一些想法

上面的內容在書中都可以找到,確實沒什麼意思。

在最初接觸時,我有點覺得基於 const 的過載是一個有些沒什麼用的特性,因為無論是常量還是非常量物件都可以呼叫 const 成員函式,那基於 const 的過載只不過是更嚴格了一些,好像用處不是很大。但其實完全不是這樣。

不妨看一個例子:

// StrBlob.h
#pragma once
#include <iostream>
#include <memory>
#include <vector>
#include <stdexcept>

using namespace std;

class StrBlob{
    friend ostream& operator << (ostream&, const StrBlob&);
    friend istream& operator >> (istream&, StrBlob&);
public:
    typedef vector<string>::size_type size_type;
    StrBlob() :data() {}
    StrBlob(initializer_list<string> il) :data(make_shared<vector<string>>(il)) {}
    void push_back(const string& s) { data->push_back(s); }
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void pop_back() const;
    string& front() const;
    string& back() const;
private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string& msg) const;
};

這裡略去了具體成員函式的部分實現。詳見《C++ Primer 5th》第十二章。

#include "StrBlob.h"

int main()
{
    StrBlob b1;
    StrBlob b2 = {"a", "an", "the"};
    b1 = b2;
    b2.push_back("some");
    const StrBlob bc = {"1", "2", "3"};
    bc.front() = "4444";
    cout << bc.front() << endl;
    return 0;
}

你會發現這部分程式碼完全可以通過編譯,而且會出現一個災難性的問題:const 物件 bc 中的值被改變了。

這完全合法(至少我直覺上認為),因為 bc 中的 data 屬性地址沒變,變化的只是 data 中的一個元素。

但是這可能並不是我們想要的,我覺得我們定義了一個 const 物件,就是希望其中的資料不會被改變,就像我們定義 const int a = 42; 一樣。

那問題出在哪呢?就出在這個 const 成員函式上,front() 顯然返回一個字串引用,修改一個字串引用繫結的物件總不是錯的吧?這就導致我們雖然好像定義了 const 物件,但是被我們自己的成員函式實現給否決了。

所以這時候就看出基於 const 的過載的作用了,他會根據 this 指標是否指向常量物件選擇過載的成員函式,一個正確的類定義應該是下面這樣:

#pragma once
#include <iostream>
#include <memory>
#include <vector>
#include <stdexcept>

using namespace std;

class StrBlob{
    friend ostream& operator << (ostream&, const StrBlob&);
    friend istream& operator >> (istream&, StrBlob&);
public:
    typedef vector<string>::size_type size_type;
    StrBlob() :data() {}
    StrBlob(initializer_list<string> il) :data(make_shared<vector<string>>(il)) {}
    void push_back(const string& s) { data->push_back(s); }
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void pop_back() const;
    string& front();
    const string& front() const;
    string& back();
    const string& back() const;
private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string& msg) const;
};

這個時候再執行上面的主函式,因為返回的是一個指向常量的字串引用,所以並不可以對其進行賦值修改。

當然還是要具體情況具體分析,我個人感覺那些返回物件資料引用或指標的就需要使用基於 const 過載的 const 成員函式。防止對 const 物件資料進行誤修改。