条款03:尽可能使用const
Use const whenever possible
const成员函数
将const实施于成员函数
目的: 确认该成员函数可以作用于const对象身上。
原因:
- 它们使得class接口更为容易理解——便于得知哪个函数可以改动对象内容,哪个不行。
- 它们使得“操作const对象”成为可能——为了改善C++的效率,根本的方法在于pass-by-reference-to-const的方式传递对象,而实现的前提,在于拥有const成员函数来处理取得的const对象。
成员函数的重载
首先,值得注意的是,两个成员函数如果只是常量性(constness)不同,可以被重载。
例子:
class TextBlock {
public:
...
const char& operator[] (std::size_t position) const //operator[] for const对象
{ return text[position]; }
char& operator[] (std::size_t position) //operator[] for non-const对象,进行了重载
{ return text[position]; }
private:
std::string text;
};
TextBlock的operator[] s的使用:
TextBlock tb("hello");
std::cout << tb[0]; //调用non-const TextBlock::operator[]
const TextBlock ctb("hello");
std::cout << ctb[0]; //调用const TextBlock::operator[]
在实际的编程中,const对象大多为pass by pointer-to-const或者passed by reference-to-const,因此对于下面这个例子更为合适:
void Print(const TextBlock& ctb)
{
std::cout << ctb[0]; //调用const TextBlock::operator[]
...
}
只要重载operator[]并对不同的版本给与不用的返回类型,就可以使得const和non-const得到不同的处理:
//对于non-const:
std::cout << tb[0]; //正确!读取一个non-const
tb[0] = 'x'; //正确!写入一个non-const
//对于const:
std::cout << ctb[0]; //正确!读取一个const
ctb[0] = 'x'; //错误!写入一个const
在上面例子的错误中,是因为operator[]的返回类型出现错误——企图对一个“由const 加之的operator[]返回”的const char& 进行赋值。
另外,在上面的non-const operator[]的返回类型是一个reference to char,而不是char。如果只是返回一个char的话:
tb[0] = 'x'; //错误!无法通过编译
出现这样的问题的原因,是因为如果函数的返回类型是一个内置类型,那么直接去修改返回值本身就是不合法的。即使合法,C++以by value返回对象也意味着,这样的改动其实是tb.text[0]的一个副本,而并非tb.text[0]自身。
成员函数时const意味着什么?这个概念分成两个观点:
bitwise constness
bitwise constness也称为physical constness。该观点中认为,成员函数只有在不更改对象之任何成员变量(static除外)时才可以说是const。也就是说,它不更改对象内的任何一个bit。这种观点也正是C++对于常量性(constness)的定义,因此,const成员函数不可以更改对象内任何non-static成员变量。
但是,在这样的观点下,有些成员函数并不具备从const的性质,但是却可以通过bitwise的测试:
比如,一个更改了“指针所指物”的成员函数并不能算作是const,但是如果只是将指针隶属于对象,而指针所指物并不隶属于对象,那么对于这样的函数称之为bitwise const并不会引发编译器的异议。
举一个例子:
class CTextBlock {
public:
...
char & operator[](std::size_t position) const //进行bitwise const声明,但是有些问题!
{ return pText[position] };
private:
char* pText;
};
在上面的这个例子可以看到,此时数据不再是存储在string内,而是char*。此时,声明的operator[]为const,而该函数却返回一个reference指向对象的内部,而operator[]也确实不会更改pText。然而:
const CTextBlock cctb("hello"); //声明一个常量对象
char* pc = &cctb[0]; //调用const operator[]获得一个指针,指向cctb的数据
*pc = 'J'; //现在,cctb的数据已经被修改了,变成了"Jello"!
虽然声明了const,隶属于成员函数的指针也没有被修改,但是指针所指物却被修改了,最终还是更改了本身不像被修改的值。
logical constness
在这种观点下,一个const成员函数是可以修改它所处理的对象内的某些bits,但是只有在客户端侦测不出来的情况下才能进行。举一个例子,CTextBlock class需要计算文本块的长度:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char* pText;
std::size_t textLength;//最近一次计算的文本块长度
bool lengthIsValid;//目前的长度是否有效
};
std::size_t CTextBlock::length() const
{
if(!lengthIsValid) {
textLength = std::strlen(pText);//错误!因为在const成员函数内,不能对textLength和
lengthIsValid = true; //lengthIsValid进行赋值!
}
return textLength;
在这里,length的实现显然不是bitwise constness,因为textLength 和 lengthIsValid 都可能被修改,而这两者的修改对于const CTextBlock对象而言虽然是可以接受的,但是对于C++而言,编译器坚持bitwise constness,为此:
- 利用C++的一个与const相关的摆动场:mutable(可变的)。mutable会释放掉non-static成员变量的bitwise constness约束:
- -
class CTextBlock {
public:
...
std::size_t length() const;
private:
char* pText;
mutable std::size_t textLength;//最近一次计算的文本块长度
mutable bool lengthIsValid;//目前的长度是否有效
};
std::size_t CTextBlock::length() const
{
if(!lengthIsValid) {
textLength = std::strlen(pText);//可以在const进行修改了
lengthIsValid = true;
}
return textLength;
在上面中,mutable使得这些变量即使在const成员函数内部,也可以被修改。