声明:
- 文中内容收集整理自《Effective C++(中文版)第三版》,版权归原书所有。
- 本内容在作者现有能力的基础上有所删减,另加入部分作者自己的理解,有纰漏之处敬请指正。
条款03:尽可能使用const
Use constwhenever possible.
const是个很奇妙的东西,自己在编程开发中深有体会,它告诉编译器和其他程序员某值应该保持不变。
const语法虽然变化多端,但并不高深莫测。
- 如果关键字const出现在*之前,代表被指物是常量;如果出现在*右边,表示指针自身是常量
char greeting[] = "Hello";
char *p = greeting; //non-const pointer, non-const data
const char *p = greeting; //non-const pointer, const data
char * const p = greeting; //const pointer, non-const data
const char * const p = greeting; //const pointer, const data
- 如果被指物是常量,则const关键字放在类型前以及放在类型后*前,两种写法意义相同。
void f1(const Widget *pw);
void f1(Widget const *pw);
STL迭代器的作用类似于T*指针。如果希望迭代器所指的东西不被改动(即希望STL模拟一个const T*指针),则使用const_iterator即可。
const最具威力的用法是面对函数声明时的应用。
- 令函数返回一个const,往往可以降低因客户原因造成的意外,而又不至于放弃安全性和高效性。eg:
//考虑有理数的operator*声明式
class Rational{…};
const Rational Rational::operator*(const Rational &lhs, const Rational &rhs);
//将返回值声明为const可避免这种赋值情况的发生
Rational a, b, c;
(a*b) = c;//这是个没意思的动作
const成员函数
将const实施与成员函数的目的,是为了确认该成员函数可作用于const对象上
- const成员函数用放置于参数列表后的const标识符标识。
- 两个成员函数,如果只是常量性不同,可以被重载。
class TestBlock
{
private:
string text;
public:
const char& operator[](size_t position) const
{
return text[position];
}
char& operator[](size_t position)
{
return text[position];
}
};
TestBlock tb("Hello");
const TestBlock ctb("World");
tb[0] = 'x'; //正确,写一个nonst-const类型
ctb[0] = 'x'; //错误,写一个const类型
- 上述错误只因operator[]的返回类型所致
- 注意:两个operator[]的返回类型都为reference to char,是因为如果函数的返回类型是个内置类型,那么改动函数返回值从来就不合法。或者即使合法,C++以by-value返回对象这一事实意味着并没有改动tb.text[0]自身,而是改动的一个副本。
- const成员函数不可以更改对象内任何non-static成员变量,eg:
class CTextBlock
{
public:
size_t length() const;
private:
char *pText;
size_t textLength;
bool lengthIsValid;
};
size_t CTextBlock::length() const
{
if (!lengthIsValid)
{
textLength = strlen(pText); //错误,表达式必须是可修改的左值
lengthIsValid = true; //错误,表达式必须是可修改的左值
}
return textLength;
}
- 解决以上问题只需要利用C++的一个与const相关的摆动场:mutable(可变的),mutable会释放掉no-static成员变量的const约束。
mutable size_t textLength;
mutable bool lengthIsValid;
在const和non-const成员函数中避免重复
两个版本的operator[]通常会有代码重复,真正该做的是实现operator[]的机能一次并使用它两次。也就是说,必须令其中一个调用另一个。本例中,const operator[]完全做掉了non-const版本该做的一切,唯一不同的是其返回类型多了一个const。这种情况下将返回值的const移除是安全的,因为不论谁调用non-const operator[]都一定首先有个non-const对象。所以令non-const版本的调用const版本的是一个避免代码重复的办法。
class TestBlock
{
private:
string text;
public:
...
const char& operator[](size_t position) const
{
...
...
...
return text[position];
} //const版本和之前一致
char& operator[] (size_t position)
{
return const_cast<char&>(static_cast<const TestBlock&>(*this)[postion]);
}
};
代码解析:
- const版本和之前一致;
- 我们打算让non-const版本调用const版本,如果non-const版本单纯调用operator[],会导致递归调用,因此要指出调用的是const版本;
- 此处将*this从起原始类型TextBlock&转型为const TextBlock&,语法为static_cast<const TextBlock&>(*this),这是第一次转型;
- 第二次转型为从const operator[]的返回值中移除const,语法为const_cast<char&>()。
需要注意的是,反向做法——即令const版本调用non-const版本是一种错误行为,因为对象可能被改动。const成员函数承诺绝对不改变对象的逻辑状态,而non-static成员函数可以对对象做任何操作。
请记住:
将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”。
党const和non-const成员函数有实质等价的实现时,令non-const版本调用const版本可避免代码重复。