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 物件資料進行誤修改。