条款03:尽可能使用const
Use const whenever possible
在const和non-const成员函数中避免重复
在上一章的介绍中,虽然mutable是一个解决办法,但是它无法解决所有的const问题。例如,在TextBlock中的operator[]不仅仅是返回一个reference以指向某一个字符,它也拥有其他的功能:
执行边界检测、记录访问信息、进行数据完整性的检测等等。
这时,如果要将这些功能同时放到const和non-const的operator[]内,会造成大量的重复的代码:
class TextBlock {
public:
...
//const:
const char& operator[] (std::size_t position) const
{
...//执行边界检测
...//记录访问信息
...//进行数据完整性的检测
return text[position];
}
//non-const:
char& operator[] (std::size_t position)
{
...//执行边界检测
...//记录访问信息
...//进行数据完整性的检测
return text[position];
}
private:
std::string text;
};
当然,上面的代码可以用另外一个private的成员函数来代替并令const和non-const operator[]来进行调用,但是依然重复了一些代码:比如函数的调用、返回语句等等。
此时,正确的做法是实现operator[]的功能一次,并使用它两次。即,必须令其中一个调用另一个。于是,我们所要做的是将常量性移除。
在例子中,const operator[]实现了non-const operator[]所要做的事,唯一的区别就是返回类型多了一个const资格修饰。
此时,如果将返回值的const转除(也就是去除其const属性),是安全的,因为无论是谁调用non-const operator[],都一定首先有一个non-const对象,否则就不能调用non-const函数。
因此,令non-从上图 operator[] 调用其const兄弟是一个避免代码重复的安全做法——即使这个过程需要一个转型动作。
class TextBlock {
public:
...
//const:和原先一样
const char& operator[] (std::size_t position) const
{
...
...
...
return text[position];
}
//non-const:发生区别,直接调用了const op[]
char& operator[] (std::size_t position)
{
return //直接return
const_cast<char&>( //将op[]返回值的const移除
static_cast<const TextBlock&>(*this) //为*this加上const
[position] //调用const op[]
);
}
...
}
在上面的代码中,发生了两次转型。
首先,我们打算让non-const operator[]调用其兄弟const,但是如果直接的去调用operator[],只会递归的调用自己,陷入调用的死循环中。为了避免这种递归的死循环,我们必须要指出调用的是const operator[],但是C++中又没有对应的方法来实现。
因此,这里将*this从其原始类型TextBlock& 转换为const TextBlock&,即使用转型操作来加上const。添加const的这一次转型强迫进行了一次安全转型(将non-const对象转为const对象)。
- 第一次,用来为*this添加 const,使得接下来调用operator[]是可以条用const的版本,使用static_cast。
- 第二次,则是从const operator[]的返回值中移除const,利用const_cast来完成。
由此,运用const成员函数实现了non-const孪生兄弟得以实现,避免了代码的重复,这种方法是值得学习和理解的。
但是,值得注意的是,反向做法——令const版本调用non-const版本以避免重复——是不应该的!。因为,const成员函数承诺了绝不改变其对象的逻辑状态(logical state),但non-const成员函数却没有这样的承诺。
如果const函数内调用了non-const,就会出现这样的风险:曾经承诺不改动的那个对象被改动了!
而原本的做法——non-const的版本去调用const版本,才是安全的,因为non-const成员函数本身就可以对其对象做任何动作,因此调用const并不会产生风险。
最后: