Logical const and physical const in C ++

Source: Internet
Author: User

Use const whenever possible

One wonderful thing about const is that it allows you to specify a semantic constraint: a specific object should not be modified. The compiler will execute this constraint. It allows you to notify the 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 help from the compiler to ensure that this value will not be changed.

The keyword const is very versatile. Outside the class, you can use it for global constants or namespace constants, just like objects declared as static within the scope of files, functions, or modules. Inside the class, you can use it for static and non-static data members. For pointers, you can specify that the pointer itself is a const, or that the data it points to is a const, or that 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 const appears on the left side of *, the pointer points to a constant. If const appears on the right side of *, the pointer itself is a constant. If const appears on both sides of *, both are constants.

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

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

Void F2 (widget const * PW); // so does F2

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

STL iterators uses pointers as the prototype, so an iterator is very similar to a T * pointer in behavior. Declaring an iterator as const is similar to declaring a pointer As const (that is, declaring a T * const pointer): You cannot point iterator to another thing, but what it points to can be changed. If you want an iterator to point to something that cannot be changed (that is, the STL equivalent of const T *), you should use 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

+ 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

The most powerful usage of const comes from its application in function declaration. In a function declaration, const can be used in both function return values and individual parameters. For a member function, it can also be used in the entire function.

A function returns a constant, which can minimize the impact of customer errors without giving up security and efficiency. For example, consider the declaration of the rational member operator * in item 24:

Class rational {...};

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

For the first time, many people will disagree. Why should the operator * result be 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 types, such code is obviously invalid. A good user-defined type is characterized by avoiding any inconsistency with the built-in type for no reason, and it seems no reason for me to allow the value assignment 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 special about the const parameters-they act like local const objects, and you should use them whenever you can. Unless you need to change the capabilities of a parameter or 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

The purpose of a member function declared as const is to ensure that the function may be called by the const object. For two reasons, such a member function is very important. First, it makes the 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. 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 an object reference to a const parameter. This technique is only available when the const candidate object has a const member function.

Many people have not noticed the fact that member functions can be overloaded only when their constants are different. 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, the const object is most often used in actual programs as a result of such an operation: Passing pointers or references to the const parameter. The CTB example above is artificial. The following example is more realistic:

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

By reloading operator [] and returning different types to different versions, you can perform different operations on textblocks of const and non-const:

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 this error occurs only when operator [] is called, and operator [] is always correct. The error occurs when an attempt is made to assign a value to const char, which is the return type of const operator.

Note that operator [] of the non-const version returns a single-character reference rather than the character itself. If operator [] returns only one character, the following statement cannot be compiled:

TB [0] = 'X ';

Because changing the return value of a built-in function is always invalid. If it is valid, the fact that C ++ returns an object with a value (by value) (see item 20) means TB. the copy of Text [0] is changed, not TB. text [0], which is not what you want.

Let's leave a little time for philosophy. What does a member function mean by const? There are two main concepts: bitwise constness (also known as physical constness) and logical constness ).

The const faction of binary bits insists that a member function, when and only when it cannot change any data member of the object (except for static members), cannot change any binary bits in the object, the member function is const. One advantage of binary constants is that it is easier to monitor violations: the compiler only needs to find values for data members. In fact, binary constants are the definition of constants in C ++. A const member function is not allowed to change any non-static data member of the object that calls it.

Unfortunately, many member functions cannot pass the binary constants test completely. In particular, a member function that often changes the content pointed to by a pointer. Unless this pointer is in this object, this function is a binary const, And the compiler will not raise any objection. For example, suppose we have a class similar to textblock, because it needs to deal with a c api that does not know why string, so it needs to store its data as char * rather than 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 internal data of an object, this class (not suitable) declares it as a const 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 generates operator [] Code happily, because it is a binary 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 use a definite value to create a constant object, and then you just use it to call the const member function, but you have changed its value! This introduces the concept of logical constants. The believer in this theory believes that when a const member function is called, some binary bits in the object may be changed, but only methods that the customer cannot feel can be used. For example, your ctextblock 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 a binary 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 the binary constants. What should I do?

The solution is simple: Use the const-related flexible space of C ++ in the form of mutable keyword. Mutable frees non-static data members from the constraints of binary constants:

Class ctextblock {
Public:
...
STD: size_t length () const;

PRIVATE:
Char * ptext;
Mutable STD: size_t textlength; // These data members may
Mutable bool 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

Mutable is a good solution to solve the problem that binary bitwise constants are not too suitable for me, but it cannot solve all the const-related problems. For example, if operator [] In textblock (including ctextblock) not only needs to return a reference to an appropriate character, it also needs to perform a boundary check, record access information, and even confirm data integrity, add these functions to the operator [] functions of const and non-const to make them a huge object 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! You mean repeated code? Are there any other headaches such as additional compilation time, maintenance costs, and code expansion? Of course, you can also transfer all the Code such as the boundary check to a separate member function (of course private), and let the two operator versions [] Call it. However, you still need to repeat the code that calls the function and returns the statement.

How can I implement the operator [] function only once and use it twice? You can use one version of operator [] to call another version. And remove constants through forced transformation.

As a general rule, forced transformation is a very bad idea. I will invest an entire item to tell you not to use it, but repeating code is not a good thing. In the current situation, the operator [] of the const version does exactly what the non-const version does. The only difference is that it has a const return type. In this case, it is safe to remove the constants of the return type through transformation, because no matter who calls non-const operator [], the first condition is that there is a non-const object. Otherwise, it is impossible for him to call a non-const function. Therefore, even if a forced transformation is required, it is safe to allow non-const operator [] to call the const version to avoid code duplication. The Code is as follows. Subsequent explanations may make your understanding of it 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 mandatory transformations in the code, but more than one. We asked non-const operator [] to call the const version. However, if we call the non-const operator [] internally, then we will recursively call ourselves 1 million times or more. To avoid infinite recursion, we must make it clear that we need to call const operator [], but there is no direct way to do this, therefore, we will forcibly transform * This from its original type textblock & to const textblock &. Yes, we use forced transformation to add const to it! So we have two forced transformations: the first is to add const to * This (the purpose is to call the const version when we call operator ), the second is to remove const from the return value of const operator.

The forced transformation of adding const is a secure conversion (from a non-const object to a const object), so we use static_cast. To remove the mandatory transformation of const, you can use const_cast. Here we have no other choice.

On the basis of other things, we call an operator in this example, so the syntax looks a bit strange. As a result, it won't win the beauty contest, but it achieves its non-const version by implementing its non-const version on the const operator [] to avoid repeated code methods to 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 on the basis of a const member function is worth mastering.

It is even more worth noting that the reverse method for doing this-by calling the non-const version with the const version to avoid code duplication-you cannot do it. Remember, a const member function promises not to change the logical state of its objects, but a non-const member function does not. If you call a non-const member function from a const member function, you are at risk that the object you promise will not change. This is why calling a non-const member function using a const member function is incorrect and the 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 in the above example, is safe: A non-const member function can do whatever it wants for an object, therefore, there is no risk to call a const member function. This is why static_cast can work here: there is no const-related risk.

As I mentioned at the beginning of this article, const is a wonderful thing. In pointers and iterators, in pointer, iterator, and reference involving objects, in function parameters and return values, in local variables, in member functions, 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 in any range of objects, for function parameters and return types, for the entire member function.

· The Compiler insists on Binary constants, but you should use the conceptual constness 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)

· When the const and non-const member functions have essentially the same implementation, calling the const version using the non-const version can avoid repeated code.

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.