Comparison of const and constexpr

This article is transferred from the Great God blog , worship here.

const

In c++, const can be used to modify variables and functions, and has different meanings for different purposes. The following is an explanation

const semantics

The purpose of const in C++ is to ensure the constant nature of the object through the compiler, forcing the compiler to treat all operations that may violate the constant nature of the const object as an error.
The constantness of objects can be divided into two types: physical constantity (that is, every bit cannot be changed) and logical constantity (that is, the behavior of the object remains unchanged). Physical constant is used in C++, such as the following example:

struct A {
    
    
    int *ptr;
};
int k = 5, r = 6;
const A a = {
    
    &k};
a.ptr = &r;  // error
*a.ptr = 7;  // no error

a is a const object, assigning any member of a will be regarded as an error, but if you do not change ptr, but change the object pointed to by ptr, the compiler will not report an error. This actually violates the logical constantness, because the performance of A has changed!

Another feature of logical constant is that there can be some domains invisible to the user in the const object, and changing them will not violate logical constant. Examples in Effective C++ are:

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

Each time the CTextBlock object calls the length method, it caches the current length in the textLength member, and the lengthIsValid object represents the effectiveness of the cache. In this scenario, if textLength and lengthIsValid are changed, it does not violate the logical constant of the CTextBlock object, but because some bits in the object are changed, it will be blocked by the compiler. To solve this problem in C++, the mutable keyword is added.

This section summarizes: The semantics of const in C++ is to ensure physical constants, but a part of logical constants can be supported through the mutable keyword.

const modified variable

As mentioned in the previous section, the semantics of modifying a variable with const requires the compiler to prevent all assignments to the variable. Therefore, the initial value of the const variable must be provided when it is initialized:

const int i;
i = 5;  // error
const int j = 10;  // ok

This initial value can be a value determined at compile time, or a value determined at runtime. If you give a compile-time initial value to a const variable of integer type, you can use this variable as the length of the array when declaring it:

const int COMPILE_CONST = 10;
const int RunTimeConst = cin.get();
int a1[COMPLIE_CONST];  // ok in C++ and error in C
int a2[RunTimeConst];  // error in C++

Because the C++ compiler can directly replace the compile-time constant in the array length with its literal value, which is equivalent to automatic macro replacement. (The gcc verification found that only the length of the array was directly replaced, and the other places assigned with COMPILE_CONST were not replaced.)

The const variable of the file domain is visible in the file by default. If you need to use the const variable M in a.cpp in b.cpp, you need to add extern at the initialization of M:

// a.cpp
extern const int M = 20;
 
// b.cpp
extern const int M;

It is generally believed that putting the definition of the variable in the .h file will cause all the .cpp files that include the .h file to have the definition of this variable, which will cause conflicts during linking. But it is possible to put the definition of const variable in the .h file. The compiler will put this variable in the anonymous namespace of each .cpp file, so it is a different variable and will not cause link conflicts. (Note: But if the initial value of the const amount in the header file depends on a certain function, and the return value of each call to this function is not fixed, it will cause the value of the const amount seen in different compilation units to be unequal . Guess: At this time, using the const amount as a static member of a certain class may solve this problem.)

const modified pointers and references

When const modifies a reference, its meaning is the same as modifies a variable. But when const modifies pointers, the rules are a bit more complicated.

Simply put, the type of a pointer variable can be divided into two parts according to the nearest'*' to the left of the variable name. The right part represents the nature of the pointer variable, and the left part represents the nature of the element it points to:

const int *p1;  // p1 is a non-const pointer and points to a const int
int * const p2;  // p2 is a const pointer and points to a non-const int
const int * const p3;  // p3 is a const pointer and points to a const int
const int *pa1[10];  // pa1 is an array and contains 10 non-const pointer point to a const int
int * const pa2[10];  // pa2 is an array and contains 10 const pointer point to a non-const int
const int (* p4)[10];  // p4 is a non-const pointer and points to an array contains 10 const int
const int (*pf)();  // pf is a non-const pointer and points to a function which has no arguments and returns a const int
...

The interpretation rules for const pointers are almost the same...

The pointer itself is const, which means that the pointer cannot be assigned, and the pointer is const, which means that the pointer cannot be assigned. Therefore, a reference can be regarded as a pointer that is const itself, and a const reference is a const Type * const pointer.

Pointers to const cannot be assigned to pointers that point to non-const, and const references cannot be assigned to non-const references, but the reverse is not a problem. This is also to ensure that const semantics are not destroyed.

You can use const_cast to remove the const property of a pointer or reference, or use static_cast to add const property to a non-const pointer or reference:

int i;
const int *cp = &i;
int *p = const_cast<int *>(cp);
const int *cp2 = static_cast<const int *>(p);  // here the static_cast is optional

The this pointer in a C++ class is a pointer whose itself is const, and the this pointer in a class's const method is a pointer whose itself and its point are both const.

Const member variables in the class

The const member variables in the class can be divided into two types: non-static constants and static constants.

Non-static constant:

The non-static constants in the class must be initialized in the initialization list of the constructor, because the non-static members in the class are constructed before entering the function body of the constructor, and the const constants must be initialized during construction. Later assignments will be blocked by the compiler.

class B {
    
    
public:
    B(): name("aaa") {
    
    
        name = "bbb";  // error
    }
private:
    const std::string name;
};

static constant:

The static constant is declared directly in the class, but the only definition and initial value outside the class is required. The common method is to include the definition of the static constant of the class in the corresponding .cpp:

// a.h
class A {
    
    
    ...
    static const std::string name;
};
 
// a.cpp
const std::string A::name("aaa");

A special case is that if the type of the static constant is a built-in integer type, such as char, int, size_t, etc., then the initial value can be given directly in the class, and there is no need to define it outside the class. The compiler will directly replace this static constant with the corresponding initial value, which is equivalent to macro replacement. But if we use this static constant like a normal variable in the code, such as taking its address instead of just using its value like a macro, then we still need to provide a definition outside the class, but no initial value is needed (Because it already exists in the statement).

// a.h
class A {
    
    
    ...
    static const int SIZE = 50; 
};
 
// a.cpp
const int A::SIZE = 50;  // if use SIZE as a variable, not a macro

const modified function

In C++, const can be used to modify the non-static member function of a class, and its semantics is to ensure the constness of the object corresponding to the function. In const member functions, all operations that may violate the constness of this pointer (this pointer in const member functions is a double const pointer) will be blocked, such as assignment to other member variables, calling their non-const methods, and calling objects The non-const method itself. But any operation done on a member variable declared as mutable will not be blocked. A certain logical constant is guaranteed here.

In addition, when const modifies the function, it also participates in the overloading of the function, that is, when calling a method through a const object, a const pointer or a reference, the const method is called first.

class A {
    
    
public:
    int &operator[](int i) {
    
    
        ++cachedReadCount;
        return data[i];
    }
    const int &operator[](int i) const {
    
    
        ++size; // !error
        --size; // !error
        ++cachedReadCount; // ok
        return data[i];
    }
private:
    int size;
    mutable cachedReadCount;
    std::vector<int> data;
};
 
A &a = ...;
const A &ca = ...;
int i = a[0]; // call operator[]
int j = ca[0]; // call const operator[]
a[0] = 2; // ok
ca[0] = 2; // !error

In this example, if the two versions of operator[] have basically the same code, consider calling another function in one of the functions to achieve code reuse (refer to Effective C++). Here we can only use the non-const version to call the const version.

int &A::operator[](int i) {
    
    
    return const_cast<int &>(static_cast<const A &>(*this).operator[](i));
}

Among them, in order to avoid calling itself to cause an infinite loop, first convert *this to const A &, which can be done using static_cast. After getting the return value of const operator[], you must manually remove its const, which can be done using const_cast. Generally speaking, const_cast is not recommended, but here we clearly know that the object we are dealing with is actually non-const, so it is safe to use const_cast here.

constexpr

constexpr is a new keyword in C++11, and its semantics is "constant expression", that is, an expression that can be evaluated at compile time. The most basic constant expression is the result returned by keywords such as literal value or global variable/function address or sizeof, while other constant expressions are obtained by basic expressions through various deterministic operations. The constexpr value can be used for enum, switch, array length and other occasions.

The variable modified by constexpr must be evaluable at compile time, and the modified function must return constexpr when all its parameters are constexpr.

constexpr int Inc(int i) {
    
    
    return i + 1;
}
 
constexpr int a = Inc(1); // ok
constexpr int b = Inc(cin.get()); // !error
constexpr int c = a * 2 + 1; // ok

constexpr can also be used to modify the constructor of a class, that is, to ensure that if the parameters provided to the constructor are all constexpr, then all members in the generated object will be constexpr, which is also a constexpr object, and can be used for various types of Where constexpr can be used. Note that the constexpr constructor must have an empty function body, that is, the initialization of all member variables is placed in the initialization list.

struct A {
    
    
    constexpr A(int xx, int yy): x(xx), y(yy) {
    
    }
    int x, y;
};
 
constexpr A a(1, 2);
enum {
    
    SIZE_X = a.x, SIZE_Y = a.y};

Benefits of constexpr:

  1. It is a strong constraint to better ensure that the correct semantics of the program is not destroyed.
  2. The translator can perform very large optimizations on the constexpr code at compile time, such as directly replacing all the constexpr expressions used with the final result.
  3. Compared with macros, there is no additional overhead, but it is safer and more reliable.

Guess you like

Origin blog.csdn.net/qq_24649627/article/details/110631381