c++ const成员函数的纠结

时间:2022-09-01 12:39:27

const的美妙在于它能让你规定一个语义上的常量。应该尽可能地在代码中使用它,全局作用域,命名空间内,类里面都可以使用, 不管是静态还是非静态的对象,你都可以使用它。

一个const对象,它只能调用const函数。因为非const函数,可能会修改这个对象。因此,如果一个成员函数,它不会修改任何非static 成员数据,你应该声明为const。比如:

class String {
public:

char& operator[](int position)
{ return data[position]; }

const char& operator[](int position) const
{ return data[position]; }
private:
string *data;
};

然而,世事不遂人愿。考虑一下代码:

class String {
public:
char& operator[](int position) const
{ return data[position]; }
private:
char *data;
};
const String BillGates = "employer";
BillGates[7] = 'e';
cout << BillGates << endl;

结果是BillGates从employer变成了employee,这不是我们期望的。一个const对象,调用一个const函数,结果把对象修改了, 你觉得这很荒唐。C++也觉得自己很委屈,因为函数确实没有修改成员数据,符合const成员函数规则。它是如此顺利地通过C++编译器的安检,没有任何的警报,看似风平浪静,实则暗藏杀机 。既然如此,难道就无解了吗?不,你只需要将char& 改成const char&即可。

问题是解决了,却牵扯出一个问题,那就是究竟如何看待const。话说,一时众说纷纭,形成两派主流:bitwise派和logic派。bitwise认为, const应该不能修改对象实体;而logic派则认为,只要客户端看不出修改即可,真正实现可以有修改对象实体。C++选择了bitwise派, 毕竟实现起来也容易的多。logic派甚为不服,给出以下例子,代码如下:

class String {
public:

size_t length() const;
private:
char *data;
size_t dataLength;
bool lengthIsValid;
};
size_t String::length() const
{
if (!lengthIsValid) {
dataLength = strlen(data); // error!
lengthIsValid = true; // error!
}
return dataLength;
}

length()仅仅是求String的长度,声明为const无可厚非;为了高效应对频繁查询,缓冲长度数据也合情合理。然而就这人畜无害的 代码,C++编译器不让通过。

啊,这真是让C++为难!可修改对象,还是不可修改对象,这是个问题,是个纠结的问题。如果选择logic派,那const形同虚设。也许Bjarne Stroustrup应该学习 Dennis Ritchie那样,相信程序员所写的代码。Bjarne Stroustrup心里知道,因为这点,无数程序员受尽了C语言的折磨。哪有人不犯错呢,他不能那么做。 然而logic派所言也并非没有道理,该怎么办呢?Bjarne Stroustrup在房间里踱来踱去,他拿不定主意。

mutable出场了。C++决定依然坚持bitwise派,同时提供mutable让logic派能够实现他们的愿望。于是乎,上面的代码就可以这么写了:

class String {
public:

size_t length() const;
private:
char *data;
mutable size_t dataLength;
mutable bool lengthIsValid;
};
size_t String::length() const
{
if (!lengthIsValid) {
dataLength = strlen(data); // ok
lengthIsValid = true; // ok
}
return dataLength;
}

终于,代码通过了编译器的安检,C++编译器是开开心心地产出目标指令。

参考:《ffective C++》