Effective C + + Chapter 1. Make yourself accustomed to C + + (Accustoming yourself to C + +) Item 3. Use const (using const whenever possible) whenever possible
1. Const and semantic constraints
const allows you to specify a semantic constraint (that is, to specify an object that should not be altered), and the compiler enforces this constraint. It can modify constants in the scope of global or namespace (see ITEM2) outside the classes, or decorate objects declared as static in a file, function, or chunk scope (block scope). You can also modify static and Non-static member variables inside the classes. In the face of a pointer, you can indicate that the pointer itself, the pointer, or both (or neither) are const:
CharGreeting[] ="Hello";Char* p = greeting;//non-const pointer, non-const dataConst Char* p = greeting;//non-const pointer, const dataChar*Constp = greeting;//const pointer, non-const dataConst Char*Constp = greeting;//const pointer, const data
The const syntax is changeable, but not inscrutable. If the keyword const appears to the left of the asterisk, indicating that the object is constant, and if it appears to the right of the asterisk, the pointer itself is a constant, and if it appears on both sides of the asterisk, both the finger and the pointer are constants.
Note: If the object is a constant, the keyword const is written before the type, and the keyword is written before the type/asterisk, both of which have the same meaning. That is, the following two functions accept the same type of parameter:
void F1 (const widget* pw); // F1 gets a pointer to a constant (immutable) Widget object void Const * pw); // F2, too .
2. Const and STL iterators
The STL iterator is modeled as a pointer, so the iterator acts like a t* pointer . Declaring an iterator as const is like declaring a pointer as const (that is, declaring a t* const pointer), which means that the iterator must not point to something different, but that the value of the object it refers to can be changed. If you want the iterator to be something that cannot be changed (that is, you want the STL to emulate a const t* pointer), you need const_iterator:
std::vector<int>Vec;Conststd::vector<int>::iterator iter = Vec.begin ();//ITER acts like a t* Const.*iter =Ten;//no problem, change the point of ITER.++iter;//Error! ITER is a conststd::vector<int>::const_iterator citer = Vec.begin ();//Citer acts like a const t*.*citer =Ten;//Error! *citer is a const++citer;//no problem, change citer.
3. Const and Function declarations
The most powerful usage of the const is the application that faces the function declaration. Within a function declaration, a const can be associated with a function return value, each parameter, the function itself (if it is a member function). The function returns a constant value, often reducing the surprises caused by customer errors without compromising security and efficiency. The operator* declaration of a rational number:
class Rational {...}; Const operator* (constconst rational& RHS);
Returning a const object prevents such behavior:
* b) = C; // call operator= on the results of a * b
This can happen by typing errors (and a type that can be implicitly converted to bool), but not easily discovered:
if (A * b = c) ... // actually want to do a comparative action
As with const parameters, they should be used when necessary to use them, just like local const objects. Declare them as const unless you need to change the parameters or local objects.
4. Const and member functions
The purpose of applying a const to a member function is to confirm that the member function can be used on a const object. This type of member function is important, based on two reasons:
- They make class excuses easier to understand. Because it is important to know which function can alter the object's content and which function is not.
- They make it possible to "manipulate a const object". This is critical for writing efficient code, because one of the fundamental ways to improve the efficiency of C + + programs is to pass objects in pass by Reference-to-const, which is feasible if a const member function is available to handle a const object that has been (and has been decorated).
Two member functions can be overloaded if they are only constant (constness), which is an important C + + feature.
classtextblock{ Public: ... Const Char&operator[] (std::size_t position)Const //operator[] for const object{returntext[position];} Char&operator[] (std::size_t position)//operator[] for Non-const object{returntext[position];}Private: std::stringtext;};
TextBlock's operator[]s can be used like this:
TextBlock TB ("Hello"<< tb[0]; // call Non-const textblock::operator[] Const TextBlock CTB ("World"<< ctb[0]; // Call Const textblock::operator[]
The const objects in the real program are mostly used for passed by Pointer-to-const or passed by Reference-to-const delivery results. The above CTB is too artificial, the following is more true:
void Print (const textblock& CTB) // This function in CTB is const{ < < ctb[0]; // Call Const textblock::operator[] ...}
As long as you overload operator[] and give different return types to different versions, you can make the const and Non-const TextBlock different processing:
std::cout << tb[0 ]; // No problem, read a non-const TextBlock tb[0 ] = " x " ; // No problem, write a non-const TextBlock std::cout << ctb[ 0 ]; // No problem, read a const TextBlock ctb[0 ] = " x " ; // error! Write a const TextBlock, operator[] Call action no problem, assign
to the const char& returned by the function
Note that the return type of Non-const operator[] is a reference to char, not a char. If operator[] just returns a char, the following sentence cannot be compiled:
tb[0'x';
Because if the return type of a function is a built-in type, the change function return value is never legal. Even if it is legal, the fact that C + + returns an object by value means that the change is actually a copy of Tb.text[0], not itself, which is not what you want.
What does a member function mean if it is const? There are two popular concepts: Bitwise Constness (also known as physical constness) and (logical constness).
The bitwise Const camp believes that member functions can be said to be const only if they do not change any of the member variables of the object (except static). This means that it does not change any bit within the object. The advantage of this argument is that it is easy to detect the point of violation: The compiler simply looks for the assignment of the member variable. Bitwise constness is the definition of C + + for const (constness), so the const member function cannot change any non-static member variable within the object.
However, many member functions can be tested by bitwise, although they are not fully const in nature. More specifically, a member function that changes the "pointer" does not count as const, but if only the pointer (not its object) is subordinate to the objects, then calling this function bitwise const does not raise a compiler objection. This leads to anti-visual results.
class ctextblock{public : ... Char operator Const // Bitwise CONST DECLARATION But in fact inappropriate return ptext[position];} Private : Char* ptext;};
This class improperly declares its operator[] as a const member function, and the function returns a reference that points to the inner value of the object (see Item 28). Assuming, for the moment, that the operator[] implementation code does not change Ptext, which is the bitwise const, the compiler outputs the target code for operator[], but:
Const Ctextblock CCTB ("hello"); // declares a constant object Char* pc = &cctb[0]; // Call Const operator[] to get a pointer to the CCTB data ' J '; // CCTB now has something like "jello."
Here you create a constant object and set it to a value, and only call the const member function on it. But you still change the value of it after all.
This situation derives from the so-called logical constness. This faction argues that a const member function can modify some bits within the object it is in, but only if the client does not detect it. For example, Ctextblock class might tell the cache text chunk length to cope with the query:
classctextblock{ Public:... std::size_t Length ()Const;Private: Char*Ptext; std::size_t textLength; //last computed chunk length of text BOOLLengthisvalid;//whether the current length is valid};std::size_t ctextblock::length ()Const{ if(!lengthisvalid) {TextLength= Std::strlen (Ptext);//Error! You cannot assign values to TextLength and lengthidvalid within a const member function. Lengthisvaild =true; } returntextLength;}
The implementation of length is not bitwise const, because both TextLength and lengthisvalid can be modified. These two data modifications are acceptable to the Const Ctextblock object, but the compiler does not agree that they persist bitwise constness. The solution is to use a const-related swing field in C + +:mutable(variable). Mutable releases the bitwise constness constraint for the non-static member variable:
classctextblock{ Public:... std::size_t Length ()Const;Private: Char*Ptext; mutable std::size_t textLength; //These member variables may always be modified, even within the const member function. mutableBOOLLengthisvalid; };std::size_t ctextblock::length ()Const{ if(!lengthisvalid) {TextLength= Std::strlen (Ptext);//now it can. Lengthisvaild =true; //also you can do that. } returntextLength;}
Some code is duplicated in the const and NON-CONST member functions, so the best way is to implement the operator[] function once and use it two times. That is, you must make one of the other calls. This prompted us to remove the constants (casting sway constness).
In the example, the const operator[] has completely done everything that the Non-const version does, and the only difference is that its return type is more of a const qualification modifier. In this case, the const-removal of the return value is safe, because no matter who calls Non-const operator[] must have a Non-const object first, otherwise the Non-const function cannot be called. So making Non-const operator[] call its const brother is a security practice that avoids code duplication-even if a transformational action is needed in the process.
classtextblock{ Public: ... Const Char&operator[] (std::size_t position)Const { ... //boundary Inspection (bounds checking)...//Log Access data...//Verify data integrity (Verify integrity) returnText[position]; } Char&operator[] (std::size_t position)//now call only const op[] { returnconst_cast<Char&> (static_cast<ConstTextblock&> (* This) [position]);//op[] Returns the const of the return value to *this plus const
//Call const op[] } ...};
This code has two transformation actions, not one, we intend to let Non-const operator[] call its const brothers, but Non-const operator[] internal if simply call operator[], will recursively call themselves. In order to avoid infinite recursion, we have to make it clear that the call is const operator[], but C + + lacks direct syntax to do so, so here the *this is transformed from its prototype textblock& to a const textblock&. So here are two transformations :
- The first time to add a const to the *this (which makes the next call to operator[] to invoke the const version);
- The second time is to remove the const from the return value of the const operator[].
The transformation that added const forced a security transformation (converting the Non-const object to a const object), so we used static_cast. The move to remove the const can only be done by const_cast, with no other option.
What's even more interesting is that the reverse approach--making the const version call the Non-const version to avoid duplication--is not something you should do. Remember, the const member function promises to never change the logical state of its objects (logical-states), but Non-const member functions have no such commitment. If you call the Non-const function inside a const function, you take the risk that the object you promised not to change is changed.
Const is a wonderful and unusual thing. On pointers and iterators, on pointers, iterators, and reference objects, on function parameters and return types, on local variables, on member functions; Use it whenever possible.
Please remember:
- Declaring something as const can help the compiler detect the error usage. A const can be applied to objects, function arguments, function return types, and member function bodies within any scope.
- The compiler enforces bitwise constness, but you should use "Conceptual constants" (conceptual constness) when you write your program.
- When the const and NON-CONST member functions have a substantially equivalent implementation, making the NON-CONST version call the const version avoids code duplication.
Effective C + + Item 3: Use const whenever possible