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 allows you 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.
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.
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.
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++.
2 {
3 public :
4 const char & operator [](std::size_t position) const // operator[] for
5 { return text[position]; } // const objects
6
7 char & operator [](std::size_t position) // operator[] for
8 { return text[position]; } // non-const objects
9
10 private :
11 std:: string text;
12 };
13
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++'s definition 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.
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:
2 {
3 public :
4 std::size_t length() const ;
5
6 private :
7 char * pText;
8 mutable std::size_t textLength; // these data members may
9 mutable bool lengthIsValid; // always be modified, even in
10 }; // const member functions
11
12 std::size_t CTextBlock::length() const
13 {
14 if ( ! lengthIsValid)
15 {
16 textLength = std::strlen(pText); // now fine
17 lengthIsValid = true ; // also fine
18 }
19
20 return textLength;
21 }
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.