Valid tive C ++, 3rd edition, item 3: Use const whenever possible

Source: Internet
Author: User

Item 3: Use const whenever possible

By Scott Meyers

Translator: fatalerror99 (itepub's nirvana)

Release: http://blog.csdn.net/fatalerror99/

One wonderful thing about const is that it allows you to specify a semantic (semantic) Constraint: a specific object (object) should not be modified. The compilers (compiler) will execute this constraint. It allows you to notify compilers (compiler) and other programmers that a value should remain unchanged. If this is the case, you should make it clear, because in this way, you can seek compilers (compiler) help to ensure that this value will not be changed.

Keyword (keyword) const is very versatile. Outside the classes (class), you can use it for constants (constants) within the global (global) or namespace (namespace) range (see item 2 ), and objects declared as static in the file, function, or block scope. Inside the classes (class), you can use it on static (static) and non-static (non-static) data members (data member. For pointers (pointer), you can specify that this pointer (pointer) is itself a const, or that it points to a const, or both are, or both are not:

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

The syntax itself is not as capricious as it is on the surface. If the const appears on the left side of the asterisk, the pointerPointed(Point to) the content is constant (constant); If the const appears on the right side of the asteriskPointer itself(Pointer itself) is constant (constant); If const appears on both sides of the asterisk, both are constant (constant ).

When the pointer points to a constant (constant), some people put the const before the type, while others put it before the asterisk after the type. The two functions have no difference in meaning. Therefore, the following two functions have the same parameter type (parameter type ):

Void F1 (ConstWidget * PW); // F1 takes a pointer to
// Constant widget object

Void F2 (widgetConst* PW); // so does F2

Because they all exist in actual code, you should get used to these two forms.

STL iterators (iterator) uses pointers (pointer) as the prototype, so an iterator is very similar to a T * pointer (pointer) in behavior ). Declaring an iterator as const is similar to declaring a pointer (pointer) as const (that is, declaring a T * const pointer (pointer )): you cannot direct this iterator to another thing, but the things it points to can change. If you want an iterator to point to an unchangeable thing (that is, a const T * pointer (pointer) STL peer), you need a const_iterator:

STD: vector <int> VEC;
...
ConstSTD: vector <int> ::IteratorIter = // ITER acts like a T * const
VEC. Begin ();
* Iter = 10; // OK, changes what ITER points
+ ITER; // error! ITER is const

STD: vector <int> ::Const_iteratorCiter = // citer acts like a const T *
VEC. Begin ();
* Citer = 10; // error! * Citer is const
++ Citer; // fine, changes citer

The most powerful usage of const comes from its application in function declarations (function declaration. In a function declaration (function declaration), const can be used either in the return value (return value) of a function or in some parameters (parameters, for member functions (member functions), it can also be used for the entire function.

A function returns a constant value (constant value), which can minimize the impact of customer errors without giving up security and efficiency. For example, consider the declaration of the operator * function of rational numbers (rational number) examined in item 24.

Class rational {...};

ConstRational operator * (const rational & LHS, const rational & RHs );

For the first time, many programmers will disagree. Why is the result of operator * a const object )? Because if it is not, the customer can commit such violence:

Rational A, B, C;
...
(A * B) = C; // invoke operator = on
// Result of a * B!

I don't know why some programmers assign values to the product of two numbers, but I know many programmers are not incompetent in doing so. All of these may come from a simple input error (requiring this type to be implicitly transformed to bool ):

If (A * B = C)... // oops, meant to do a comparison!

If A and B are built-in type (built-in type), such code is obviously invalid. A good user-defined types (user-defined type) feature is to avoid any inconsistency with built-ins (built-in type) for no reason (see item 18 ), in addition, it seems no reason for me to assign values to the product of two numbers. This can be avoided by declaring the return value of operator * as const, which is the reason for doing so.

There is nothing new about const parameters (parameters)-They act like local (local) const objects (objects), and whenever you can, you should use it like this. Unless you need to change the capabilities of a parameter (parameter) or local object (local object), make sure to declare it as Const. It only requires you to enter six characters to save the annoying error you just saw: "I want to type '= ', but I accidentally typed '= '".

Const member functions (const member function)

Member functions (member function) is declared as const to indicate that this member functions (member function) may be called by const objects (object. For two reasons, such member functions are very important. First, it makes the interface (Interface) of a class easier to understand. It is important to know which function can change the object and which function cannot. Second, they can work with const objects (objects. Because writing efficient code has a very important aspect, as explained in item 20, the basic way to improve the performance of a C ++ program is to pass objects by reference-to-const (to pass an object in the form of a const reference ). This technique is feasible only when const member functions (member function) and const-qualified objects (Objects modified by const) as operation results exist.

Many people have not noticed the fact that member functions (member function) can be overloaded (overloaded) only when constness (constant) is not at the same time, but this is an important feature of C ++. Consider a class that represents text blocks:

Class textblock {
Public:
...
Const char &OPERATOR [](STD: size_t position)Const// Operator []
{Return text [position];} // const objects

Char &OPERATOR [](STD: size_t position) // operator []
{Return text [position];} // non-const objects

PRIVATE:
STD: String text;
};

The operator [] s of textblock may be used as follows:

Textblock Tb ("hello ");
STD: cout <TB [0]; // callnon-const
// Textblock: operator []
Const textblock CTB ("world ");
STD: cout <CTB [0]; // callconst textblock: operator []

By the way, const objects (object) most often appears in the actual program as the result of such an operation: passed by pointer-or reference-to-const (transmitted by pointer or reference to const ). The CTB example above is artificial. The following example is more realistic:

Void print (ConstTextblock & CTB) // in this function, CTB is const
{
STD: cout <CTB [0]; // callconst textblock: operator []
...
}

Through overloading (reload) operator [], you can perform different operations on textblocks of const and non-const for different versions of return types:

STD: cout <TB[0]; // fine-reading
// Non-const textblock

TB[0] = 'X'; // fine-writing
// Non-const textblock

STD: cout <CTB[0]; // fine-reading
// Const textblock

CTB[0] = 'X'; // error! -Writing
// Const textblock

Note that the error here is only the same as that of the called operator [].Return type(Return type), while calling operator [] is always correct. The error occurs when you attempt to assign a value to const Char because it is the return type (return type) of the const operator ).

Note that the return type (return type) of non-const operator [] isReferenceTo a char (a char reference) instead of a char itself. If operator [] Only returns a simple char, the following statement cannot be compiled:

TB [0] = 'X ';

Because changing the return value of a function that returns built-in type (built-in type) is always invalid. Even if it is valid, the fact that C ++ returns objects by value (returns an object by passing a value) (see item 20) means that it is changed to TB. text [0]Copy(Copy), instead of TB. Text [0], this is not what you want.

Let's leave a little time for philosophy. What does a member function (member function) mean by const? There are two main concepts:Bitwise constness(Binary constants) (also knownPhysical constness(Physical constants) andLogical constness(Logical constants ).

Bitwise (Binary) const faction insists that a member function (member function), when and only when it does not change any data members (data member) of an object) (except static). That is to say, if no bits (Binary bit) in the object is changed, the member function (member function) is const. One advantage of bitwise constness (Binary constants) is that it is easier to monitor violations: the compiler only needs to find assignments (assign values) for data members (data members ). In fact, bitwise constness (Binary constants) is the definition of constness (constants) in C ++, a const member function (member function) it is not allowed to change any non-static data members (non-static data member) of the object that calls it ).

Unfortunately, many member functions (member functions) that are not fully const have passed the bitwise (Binary bit) test. In particular, the effect of a member function (member function) that often changes the content pointed by a pointer (pointer) is not Const. UnlessPointer(Pointer) in this object, otherwise this function is bitwise (Binary bit) const, And the compiler will not raise any objection. For example, assume that we have a textblock-like class (similar to the textblock class), because it needs to deal with a c api that does not know what the string objects (object) is, therefore, it needs to store its data as char * instead of string.

Class ctextblock {
Public:
...

Char & operator [] (STD: size_t position)Const// Inappropriate (but bitwise

{Return ptext [position];} // const) Declaration
// Operator []
PRIVATE:
Char * ptext;
};

Although operator [] returns a reference to the object's internal data (a reference to the object's internal data), this class (class) is still (inappropriate) declare it as a const member function (member function) (item 28 will talk about an in-depth topic ). Put it aside first and look at the implementation of operator []. It does not use any means to change ptext. As a result, the compiler happily generates the operator [] Code, because, after all, it is bitwise (Binary bit) const for all compilers, but let's look at what will happen:

Const ctextblock CCTB ("hello"); // declare constant object

Char * Pc = & CCTB [0]; // call the const operator [] To Get
// Pointer to CCTB's data

* Pc = 'J'; // CCTB now has the value "Jello"

This is indeed a problem. You create a constant object (constant object) with a particle value (definite value), and then you just use it to call const member functions (member function ), but you have changed its value!

This introduces the concept of logical constness (logical constant. Those who believe this theory believe that a const member function (member function) may change some bits (Binary bits) in the object (object) that calls it ), however, you can only use methods that customers cannot perceive. For example, your ctextblock class (class) can store the length of text blocks as needed:

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;
}

The implementation of length is certainly not bitwise (Binary bit) const -- both textlength and lengthisvalid may be changed -- but it is still regarded as effective for the const ctextblock object. But the compiler does not agree. It still sticks to bitwise constness (Binary bit constants). What should we do?

The solution is simple: Use the const-related (const-related) flexible space known as mutable C ++. Mutable frees non-static data members (non-static data members) from the constraints of bitwise constness (Binary constants:

Class ctextblock {
Public:

...

STD: size_t length () const;

PRIVATE:
Char * ptext;

MutableSTD: size_t textlength; // These data members may
MutableBool lengthisvalid; // 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;
}

Avoid repeated const and non-const member functions (member functions)

Mutable is a good solution for solving the problem of bitwise-constness-is-not-what-I-had-in-mind (Binary bit constants are not exactly what I want, however, it cannot solve all the const-related (const-related) problems. For example, assume that operator [] In textblock (including ctextblock) not only needs to return a reference of an appropriate character, but also needs to perform bounds checking (border check ), logged access information, or even data integrity validation ), add these functions to the operator [] functions of const and non-const (you don't have to worry about implicitly inline functions (implicit inline function, see item 30) to make them a giant thing like the following:

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;
};

Oh! Do you mean code duplication (repeated code )? Are there other headaches such as additional compilation time, maintenance costs, and code expansion? Of course, you can also transfer all bounds checking and other code to a separate member function (member function) (naturally private, and let the operator [] of the two versions call it. However, you must repeat the code that calls the function and return statement.

What you really want to do is to implement the operator [] function only once and use it twice. In other words, you can use one operator [] to call another version. And the constness (constant) for our casting away (removed by forced transformation ).

As a general rule, casting (forced transformation) is a very bad idea. I will devote the whole length of an item to tell you not to use it (item 27 ), but code duplication is not a good thing. In the current situation, what const operator [] Does is exactly what the non-const version does, the only difference is that it has a const-qualified return type (the return type modified by const ). In this case, the const of the return value (return type) of casting away (removed through forced transformation) is safe, because no matter who calls non-const operator [], first, you must have a non-const object (object ). Otherwise, it cannot call a non-const function. Therefore, even if you need a cast (forced transformation), making non-const operator [] Call the const version is also a safe way to avoid repeated code. The Code is as follows. your understanding of the Code after reading the subsequent explanation may be clearer:

Class textblock {
Public:

...

Const char & operator [] (STD: size_t position) const // same as before
{
...
...
...
Return text [position];
}

Char & operator [] (STD: size_t position) // now just CILS const op []
{
Return
Const_cast<Char &> (// cast away const on
// Op []'s return type;
Static_cast<Const textblock &> (* This) // Add const to * This's type;
[Position] // call const version of OP []
);
}

...

};

As you can see, there are two casts in the code, not one. We asked non-const operator [] to call the const version. However, if we only call operator [] within the non-const operator [], we will recursively call ourselves. It performs 1 million or more times. To avoid infinite recursion (infinite recursion), we must make it clear that we want to call const operator [], but there is no direct way to do this, therefore, we will forcibly transform * This from textblock &'s natural type to const textblock &. Yes, we use cast (force transformation) to add const to it! So we have two casts (forced transformation): the first is to add const to * This (so that we can call its const version when we call operator ), the second is to remove const from the return value (return value) of const operator.

In addition, the cast (forced transformation) of const only forces a secure conversion (from a non-const object (object) to a const object (object )), so we use a static_cast. Remove const only through const_cast, so we have no other options here. (Technically, we have. A c-style cast (forced transformation of the C style) can also work, but, as I explained in item 27, casts (forced transformation) it is rarely the right choice. If you are not familiar with static_cast or const_cast, item 27 contains an overview .)

On the basis of other things, we call an operator in this example, so the syntax looks strange. As a result, it won't win the beauty contest, but it achieves its non-const version based on the const operator [] to avoid code duplication (Code duplication) and achieve the expected effect. Whether it is worthwhile to use ugly syntax to achieve the goal is determined by yourself, but this non-const version of technology that implements it based on the const member function (member function) is worth mastering.

What's even more worth noting is the reverse method for doing this-you can't do it by calling the non-const version with the const version to avoid repetition. Remember, a const member function (member function) promises never to change the logical state of its object (object), but a non-const member function (member function) will not make such a commitment. If you call a non-const member function from a const member function (member function), you are at risk of changing the object (object) that you promise not to change. This is why it is wrong to call a non-const member function (member function) using a const member function (member function), and the object (object) may be changed. In fact, if you want to compile such code, you must use a const_cast to remove the * This const, which is obviously troublesome. The reverse call-as I used above-is secure: A non-const member function (member function) can do whatever it wants for an object, therefore, calling a const member function is not risky. This is why static_cast can work on * this in this case: there is no const-related risk.

As I said at the beginning of this item, const is a wonderful thing. On pointers and iterators, on the objects involved in pointers, iterators, and references, on function parameters (function parameter) and return types (return value), on local variables (local variable) and on member functions (member function), const is a powerful ally. As long as you can use it, you will be happy with what you have done.

Things to remember

  • Declaring something as const helps the compiler discover usage errors. Const can be used for objects (objects) in any scope, for function parameters (function parameters) and return types (return types), for the entire member functions (member functions ).
  • The compiler insists on bitwise constness (Binary bit constants), but you should use conceptual constness (conceptual constants) for programming. (The original text here is incorrect. Conceptual constness is the name of the author for the logical constness in the second edition of this book. The title in the text is changed, but it is not changed here. In fact, this is the newly added part of the author, but the old term is used. It's strange! -- Translator's note)
  • When const and non-const member functions (member functions) have essentially the same implementation, calling the const version using the non-const version can avoid code duplication (Code duplication ).
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.