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 对象数据进行误修改。