Item3: Use const whenever possible
第3条:尽量使用常量const 关键字
关键字定义了一个object是不可被修改的,只要使用了const这个关键字去修饰object,就表示在其他的代码中,都不能修改这个object.
如果其他代码中对这个object有修改,编译器会报错的。
其实,只要你用了const这个关键字,就相当于告诉其他程序员,哪些地方是不能被修改的。
Const,在类内类外都可以使用,const也可以修改指针。
在类外,可以在全局或者某个命名空间内使用,,还可以用来修饰在文件中的object,函数中的object, 一个程序块中的object,
在类内,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出现在*号左边,指针指向的数据是const的。
Const出现在*号右边,指针是const的。
Const在*号的左右两边都出现,指针与其所指向的数据都是const的。
void f1(const Widget *pw); // f1 takes a pointer to a // constant Widget object
void f2(Widget const *pw); // so does f2
STL的迭代器就是指针,所以定义他们为const,就是定义指针为const.
STL的写法有点不同,如下:
std::vector<int> vec;
...
const std::vector<int>::iterator iter = // iter acts like a T* const
vec.begin();
*iter = 10; // OK, changes what iter points to
++iter; // error! iter is const
std::vector<int>::const_iterator cIter = //cIter acts like a const T*
vec.begin();
*cIter = 10; // error! *cIter is const
++cIter; // fine, changes cIter
const_iterator 是定义指向的内容不能被修改,const是指指针本身不能被修改。
Const去修饰函数会比较有用。 比如去修饰函数的返回值,修饰函数的参数,修饰类的成员函数。
用const去修饰函数的返回值,能够在不增加复杂度,不增加计算量的情况下,减少bug.
比如在代码:复数的乘法中,
class Rational {... };
const Rational operator*(const Rational& lhs, const Rational& rhs);
很多程序员在一开始不以为然,但是用着用着就会发现“真香”。
为什么复数的乘法返回值必须是const?
因为如果不这么做,以下非常暴力的代码就可能发生:
Rational a, b, c;
...
(a * b) = c; // invoke operator= on the
// result of a*b!
我不理解为啥要给两个数的乘积复制,但是就是有程序员这么干,但是我知道,大部分程序员是无意这个干的。比如:
if (a * b = c)... // oops, meant to do a comparison!
哦,原来这个程序员是想做个对比,不是赋值。
像这样的代码,如果a,b是内建类型的话,编译器是一定会报错的。但是如果a,b不是内建类型,而是自定义类型,就会不报错了。那么这里我们就很迷惑了,到底是赋值还是比较? 所以出现这种迷惑的情况,我们就需要编译器报错了,如何让编译器报错?
定义乘法操作的返回值为const就可以了
关于函数的返回值,我们就先看这一个例子。
那么函数的参数是否有必要声明为const?
只要你不需要修改这个参数,就声明为const,你只用打出这6个字符,就能够让编译器来帮你查找错误。
就能避免你本以为是执行的“==”比较,但是你却命令程序执行的“=”赋值操作。
用const修饰类内的成员函数时
Const 修饰成员函数主要为const objects来调用。
Const修饰成员函数是很重要的应用,为什么?
首先,代码会易理解,很容易就看出来,一些函数不会修改类本身。
其实 , 常bjects只能访问常成员函数。
另外,要实现运行起来高效的C++代码,该参数传递为参数引用的传递,是最高效的,传递这个引用就是常量,所以就需要 const修饰的成员函数来操作这个const 引用了。
大家容易忽略一个现象,其实参数是否const,会造成成员函数重载的。 仅仅因为参数变为const,就会发生函数的重载。类中,函数重载,就相当于这是两个不同的函数,虽然函数名字相同,但是因为函数参数不同,返回值不同,它就是不同的函数。
这里是一段重载的代码样例。
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // operator[] for
{ return text[position]; } // const objects
char& operator[](std::size_t position) // operator[] for
{ return text[position]; }
private:
std::string text;
};
// non-const objects
TextBlock 's operator[] s can be used like this:
TextBlock tb("Hello");
std::cout << tb[0]; // calls non-const
// TextBlock::operator[]
const TextBlock ctb("World");
std::cout << ctb[0]; // calls const TextBlock::operator[]
上面的代码是个示范,实际项目中的代码如下:
void print(const TextBlock& ctb) // in this function, ctb is const {
std::cout << ctb[0]; // calls const TextBlock::operator[] ...
}
这里成员函数operator[]发生了重载,它有两个版本的函数,const和non-const的:
TextBlock s handled differently:
std::cout << tb[0]; // fine — reading a
// non-const TextBlock
tb[0] = 'x'; // fine — writing a
// non-const TextBlock
std::cout << ctb[0]; // fine — reading a
// const TextBlock
ctb[0] = 'x'; // error! — writing a
// const TextBlock
要给const 版本的operator[]函数返回的const char&赋值时,发生了错误。因为返回的是const修饰的char&.
如果函数返回一个内置类型的变量,那么这个变量是被规定为:不能被都改。
即使能被修改,你修改的也是一个复制,而不是原来的变量,这显然违背了你的初衷
其实,用const来修饰成员函数,意味着什么呢?有什么意义呢?
其实用const来修饰成员函数,无非就是让编译器能够发现在函数中对const objects的修改,编译器能够保证的这种const性质,我们成为bitwise constness.
那么保证了bitwise constness后,在一系列的代码中,是有可能对const objects进行修改的,
如下代码:
class CTextBlock {
public:
...
char& operator[](std::size_t position) const
// inappropriate (but bitwise
{ return pText[position]; }
private:
char *pText;
};
const CTextBlock cctb("Hello"); // declare constant object char *pc = &cctb[0]; // call the const operator[] to get a
// pointer to cctb's data
*pc = 'J'; // cctb now has the value "Jello"
就成功地把const object cctb由”Hello”修改成了”Jello”
也就是说,在程序逻辑上, 要保证const object不被修改,编译器就无法帮忙了,需要程序员自己来保证。这种需要称需要自己从逻辑上来保证const object不被修改的性质,就称为 logical constness.
我们在使用的时候,要注意logical constness.
其实,logical constness才是我们的目的。bitwise constness其实是实现logical constness的其中一个方法。
因为保证了logical constness,才能够程序按照我们的预想工作。
那有时候,为了logical constness,不免要放弃bitwise constness.
比如,我们要给出一个const 字符串的 长度,只是返回字符串的长度,这个是logical const的。
一般给出字符串长度的代码如下:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
std::size_t textLength; // last calculated length of textblock bool lengthIsValid; // whether length is currently valid
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength=std::strlen(pText);// error! can't assign to textLength
lengthIsValid = true; // and lengthIsValid in a const } // member function
return textLength;
}
显然,这样做会报错,因为在const 修饰的成员函数里,不允许发生对对象的改变。
此时,为了实现logical constness,可以适当地不在遵守bitwise constness的规则,我们需要想个办法,告诉编译器,这里是特殊情况,你不用报错,你要允许我改变textLength, lengthIsValid 的值
具体怎么办?怎么告诉编译器呢?
使用 关键字:mutable
代码改为如下:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength; mutable bool lengthIsValid;
// these data members may
// always be modified, even in
}; // const member functions
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText);// now fine
lengthIsValid = true;// also fine
}
return textLength;
}
这样,就可以实现我们的logical constness了。
实际上是利用编译器本身保证的bitwise constness,结合关键字mutable,告诉编译器,对有些变量是可以改变的,来实现最终的,const 成员函数的功能。在逻辑上是const 成员函数。
注意到,const修饰的成员函数会发生重载,其实就是两个不同的函数。
这两个不同的函数中,不同点仅仅是因为参数和返回值的类型不同,其实功能基本一样,所以,会出现大量重复的代码,如下:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const {
... // do bounds checking
... // log access data
... // verify data integrity
return text[position];
}
char& operator[](std::size_t position)
{
... // do bounds checking
... // log access data
...// verify data integrity
return text[position];
}
private:
std::string text;
};
在代码中,出现大量的重复代码,我们修改时,就必须修改两处,这显然不如何高效变成的习惯。
怎么办?
把两个函数代码中的重复代码单独抽取出来,放在一个函数中,然后分别在两个函数中调用么?
其实有更好的方法。
先实现一个函数,然后用在另外一个函数中,去调用已经实现的函数即可。这可能需要做一些类型转换,把const object 修改为object,或者把object 修改为const object.
避免重复,就要使用类型转换,虽然类型转换很不好,但是代码重复也很难受,所以,我们选择使用类型转换。
先完成一个常函数,然后在普通函数中调用常函数,
这里要做两次类型转化,调用常函数前,需要把对象本身转化为常的,还需要把常函数返回的值转化为非常的
转换为常的用static_cast
转化为非常的用const_cast
为什么不先实现普通函数,然后用常函数去调用它?
常函数就是指不改变任何变量,而普通函数就是要在内部对普通对象就行操作的,这点冲突了。
关于const的使用,以下是好的习惯:
声明const后,编译器能够帮忙查找错误。const可以在在很多地方,全局的,局部的,类内,类外,函数参数,函数返回值,以及类的成员函数。
编译器能够保证bitwise constness,但是不一定保证logical constness,你必须保证logical constness.
保证代码不重复的方法就是:先实现普通的成员函数,在const 成员函数中,去调用普通的成员函数即可。
附上英文原版:
Item 3: Use const whenever possible
The wonderful thing about const is that it allows you to specify a semantic constraint — a particular object should not be modified — and compilers will enforce that constraint. It allowsyou to communicate to both compilers and other programmers that a value should remain invariant. Whenever that is true, you should be sure to say so, because that way you enlist your compilers' aid in making sure the constraint isn't violated.
The const keyword is remarkably versatile. Outside of classes, you can use it for constants at global or namespace scope (see Item 2), as well as for objects declared static at file, function, or block scope. Inside classes, you can use it for both static and non-static data members. For pointers, you can specify whether the pointer itself is const, the data it points to is const, both, or neither:
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
This syntax isn't as capricious as it may seem. If the word const appears to the left of the asterisk, what's pointed to is constant; if the word const appears to the right of the asterisk, the pointer itself is constant; if const appears on both sides, both are constant.
When what's pointed to is constant, some programmers list const before the type. Others list it after the type but before the asterisk. There is no difference in meaning, so the following functions take the same parameter type:
void f1(const Widget *pw); // f1 takes a pointer to a // constant Widget object
void f2(Widget const *pw); // so does f2
Because both forms exist in real code, you should accustom yourself to both of them.
STL iterators are modeled on pointers, so an iterator acts much like a T* pointer. Declaring an iterator const is like declaring a pointer const (i.e., declaring a T* const pointer): the iterator isn't allowed to point to something different, but the thing it points to may be modified. If you want an iterator that points to something that can't be modified (i.e., the STL analogue of a const T* pointer), you want a const_iterator :
std::vector<int> vec;
...
const std::vector<int>::iterator iter = // iter acts like a T* const
vec.begin();
*iter = 10; // OK, changes what iter points to
++iter; // error! iter is const
std::vector<int>::const_iterator cIter = //cIter acts like a const T*
vec.begin();
*cIter = 10; // error! *cIter is const
++cIter; // fine, changes cIter
Some of the most powerful uses of const stem from its application to function declarations. Within a function declaration, const can refer to the function's return value, to individual parameters, and, for member functions, to the function as a whole.
Having a function return a constant value often makes it possible to reduce the incidence of client errors without giving up safety or efficiency. For example, consider the declaration of the operator* function for rational numbers that is explored in Item 24.
class Rational {... };
const Rational operator*(const Rational& lhs, const Rational& rhs);
Many programmers squint when they first see this. Why should the result of operator* be a const object? Because if it weren't, clients would be able to commit atrocities like this:
Rational a, b, c;
...
(a * b) = c; // invoke operator= on the result of a*b!
I don't know why any programmer would want to make an assignment to the product of two numbers, but I do know that many programmers have tried to do it without wanting to. All it takes is a simple typo (and a type that can be implicitly converted to bool):
if (a * b = c)... // oops, meant to do a comparison!
Such code would be flat-out illegal if a and b were of a built-in type. One of the hallmarks of good user-defined types is that they avoid gratuitous incompatibilities with the built-ins (see also Item 18), and allowing assignments to the product of two numbers seems pretty gratuitous to me. Declaring operator* 's return value const prevents it, and that's why it's The Right Thing To Do.
There's nothing particularly new about const parameters — they act just like local const objects, and you should use both whenever you can. Unless you need to be able to modify a parameter or local object, be sure to declare it const. It costs you only the effort to type sixcharacters, and it can save you from annoying errors such as the "I meant to type '==' but I accidently typed '='" mistake we just saw.
const Member Functions
The purpose of const on member functions is to identify which member functions may be invoked on const objects. Such member functions are important for two reasons. First, they make the interface of a class easier to understand. It's important to know which functions may modify an object and which may not. Second, they make it possible to work with const objects. That's a critical aspect of writing efficient code, because, as Item 20 explains, one of the fundamental ways to improve a C++ program's performance is to pass objects by reference-to- const. That technique is viable only if there are const member functions with which to manipulate the resulting const -qualified objects.
Many people overlook the fact that member functions differing only in their constness can be overloaded, but this is an important feature of C++. Consider a class for representing a block of text:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // operator[] for
{ return text[position]; } // const objects
char& operator[](std::size_t position) // operator[] for
{ return text[position]; }
private:
std::string text;
};
// non-const objects
TextBlock 's operator[] s can be used like this:
TextBlock tb("Hello");
std::cout << tb[0]; // calls non-const
// TextBlock::operator[]
const TextBlock ctb("World");
std::cout << ctb[0]; // calls const TextBlock::operator[]
Incidentally, const objects most often arise in real programs as a result of being passed by pointer- or reference-to-const. The example of ctb above is artificial. This is more realistic:
void print(const TextBlock& ctb) // in this function, ctb is const
{
std::cout << ctb[0]; // calls const TextBlock::operator[]
...
}
//By overloading operator[] and giving the different versions different return types, you can
//have const and non-const
std::cout << tb[0];// fine — reading a non-const TextBlock
tb[0] = 'x'; // fine — writing a non-const TextBlock
std::cout << ctb[0];// fine — reading a
// const TextBlock
ctb[0] = 'x'; // error! — writing a
// const TextBlock
Note that the error here has only to do with the return type of the operator[] that is called; the calls to operator[] themselves are all fine. The error arises out of an attempt to make an assignment to a const char&, because that's the return type from the const version of operator[].
Also note that the return type of the non-const operator[] is a reference to a char — a char itself would not do. If operator[] did return a simple char, statements like this wouldn't compile:
tb[0] = 'x';
That's because it's never legal to modify the return value of a function that returns a built-in type. Even if it were legal, the fact that C++ returns objects by value (see Item 20) would mean that a copy of tb.text[0] would be modified, not tb.text[0] itself, and that's not the behavior you want.
Let's take a brief time-out for philosophy. What does it mean for a member function to be const ? There are two prevailing notions: bitwise constness (also known as physical constness) and logical constness.
The bitwise const camp believes that a member function is const if and only if it doesn't modify any of the object's data members (excluding those that are static), i.e., if it doesn't modify any of the bits inside the object. The nice thing about bitwise constness is that it's easy to detect violations: compilers just look for assignments to data members. In fact, bitwise constness is C++'sdefinition of constness, and a const member function isn't allowed to modify any of the non- static data members of the object on which it is invoked.
Unfortunately, many member functions that don't act very const pass the bitwise test. In particular, a member function that modifies what a pointer points to frequently doesn't act const. But if only the pointer is in the object, the function is bitwise const, and compilers won't complain. That can lead to counterintuitive behavior. For example, suppose we have a TextBlock -like class that stores its data as a char* instead of a string, because it needs to communicate through a C API that doesn't understand string objects.
class CTextBlock {
public:
...
char& operator[](std::size_t position) const
// inappropriate (but bitwise
{ return pText[position]; }
private:
char *pText;
};
// const) declaration of
// operator[]
This class (inappropriately) declares operator[] as a const member function, even though that function returns a reference to the object's internal data (a topic treated in depth in Item 28). Set that aside and note that operator[] 's implementation doesn't modify pText in any way. As a result, compilers will happily generate code for operator[] ; it is, after all, bitwiseconst, and that's all compilers check for. But look what it allows to happen:
const CTextBlock cctb("Hello"); // declare constant object
char *pc = &cctb[0]; // call the const operator[] to get a
// pointer to cctb's data
*pc = 'J'; // cctb now has the value "Jello"
Surely there is something wrong when you create a constant object with a particular value and you invoke only const member functions on it, yet you still change its value!
This leads to the notion of logical constness. Adherents to this philosophy argue that a const member function might modify some of the bits in the object on which it's invoked, but only in ways that clients cannot detect. For example, your CTextBlock class might want to cache the length of the textblock whenever it's requested:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
std::size_t textLength; // last calculated length of textblock bool lengthIsValid; // whether length is currently valid
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength=std::strlen(pText);// error! can't assign to textLength
lengthIsValid = true; // and lengthIsValid in a const } // member function
return textLength;
}
This implementation of length is certainly not bitwise const — both textLength and lengthIsValid may be modified — yet it seems as though it should be valid for const CTextBlock objects. Compilers disagree. They insist on bitwise constness. What to do?
The solution is simple: take advantage of C++'s const -related wiggle room known as mutable. mutable frees non-static data members from the constraints of bitwise constness:
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength;
mutable bool lengthIsValid; // these data members may
// always be modified, even in // const member functions
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText);// now fine
lengthIsValid = true;// also fine
}
return textLength;
}
Avoiding Duplication in const and Non-const Member Functions
mutable is a nice solution to the bitwise-constness-is-not-what-I-had-in-mind problem, but it doesn't solve all const -related difficulties. For example, suppose that operator[] in TextBlock (and CTextBlock) not only returned a reference to the appropriate character, it also performed bounds checking, logged access information, maybe even did data integrity validation. Putting all this in both the const and the non-const operator[] functions (and not fretting that we now have implicitly inline functions of nontrivial length — see Item 30) yields this kind of monstrosity:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const {
... // do bounds checking
... // log access data
... // verify data integrity
return text[position];
}
char& operator[](std::size_t position) {
... // do bounds checking
... // log access data
...// verify data integrity
return text[position];
}
private:
std::string text;
};
Ouch! Can you say code duplication, along with its attendant compilation time, maintenance, and code-bloat headaches? Sure, it's possible to move all the code for bounds checking, etc. into a separate member function (private, naturally) that both versions of operator[] call, but you've still got the duplicated calls to that function and you've still got the duplicated return statement code.
What you really want to do is implement operator[] functionality once and use it twice. That is, you want to have one version of operator[] call the other one. And that brings us to casting away constness.
As a general rule, casting is such a bad idea, I've devoted an entire Item to telling you not to do it (Item 27), but code duplication is no picnic, either. In this case, the const version of operator[] does exactly what the non-const version does, it just has a const -qualified return type. Casting away the const on the return value is safe, in this case, because whoever called the non-const operator[] must have had a non-const object in the first place. Otherwise they couldn't have called a non-const function. So having the non-const operator[] call the const version is a safe way to avoid code duplication, even though it requires a cast. Here's the code, but it may be clearer after you read the explanation that follows:
class TextBlock {
public:
...
// same as before
const char& operator[](std::size_t position) const {
...
...
...
return text[position];
}
// now just calls const op[]
char& operator[](std::size_t position)
{
return
const_cast<char&>( // cast away const on // op[]'s return type;
static_cast<const TextBlock&> // add const to *this's type;
(*this)[position] // call const version of op[]
); }
... };
As you can see, the code has two casts, not one. We want the non-const operator[] to call the const one, but if, inside the non-const operator[], we just call operator[], we'll recursively call ourselves. That's only entertaining the first million or so times. To avoid infinite recursion,wehavetospecifythatwewanttocalltheconst operator[],butthere'snodirect way to do that. Instead, we cast *this from its native type of TextBlock& to const TextBlock&. Yes, we use a cast to add const ! So we have two casts: one to add const to *this (so that our call to operator[] will call the const version), the second to remove the const from the const operator[] 's return value.
The cast that adds const is just forcing a safe conversion (from a non-const object to a const one), so we use a static_cast for that. The one that removes const can be accomplished only via a const_cast, so we don't really have a choice there. (Technically, we do. A C-style cast would also work, but, as I explain in Item 27, such casts are rarely the right choice. If you're unfamiliar with static_cast or const_cast, Item 27 contains an overview.)
On top of everything else, we're calling an operator in this example, so the syntax is a little strange. The result may not win any beauty contests, but it has the desired effect of avoiding code duplication by implementing the non-const version of operator[] in terms of the const version. Whether achieving that goal is worth the ungainly syntax is something only you can determine, but the technique of implementing a non-const member function in terms of its const twin is definitely worth knowing.
Even more worth knowing is that trying to do things the other way around — avoiding duplication by having the const version call the non-const version — is not something you want to do. Remember, a const member function promises never to change the logical state of its object, but a non-const member function makes no such promise. If you were to call a non-const function from a const one, you'd run the risk that the object you'd promised not to modify would be changed. That's why having a const member function call a non-const one is wrong: the object could be changed. In fact, to get the code to compile, you'd have to use a const_cast to get rid of the const on *this, a clear sign of trouble. The reverse calling sequence — the one we used above — is safe: the non-const member function can do whatever it wants with an object, so calling a const member function imposes no risk. That's why a static_cast works on *this in that case: there's no const -related danger.
As I noted at the beginning of this Item, const is a wonderful thing. On pointers and iterators; on the objects referred to by pointers, iterators, and references; on function parameters and returntypes; on local variables; and on member functions, const is a powerful ally. Use it whenever you can. You'll be glad you did.
Things to Remember
- ? Declaring something const helps compilers detect usage errors. const can be applied to objects at any scope, to function parameters and return types, and to member functions as a whole.
- ? Compilers enforce bitwise constness, but you should program using conceptual constness.
- ? When const and non-const member functions have essentially identical implementations, code duplication can be avoided by having the non-const version call
- the const version.