C++ variable bound

C++ variable definition means that special restrictions can be added on the basis of the variable type, mainly including: whether it is const, whether it is volatile, whether it is an lvalue or an rvalue, whether it is a reference, whether it is an lvalue reference or an rvalue reference, etc.

1. Why study this thing

Mainly after c++11, move construction (the distinction between lvalue and rvalue, std:move() interface), type inference (template, auto) and other features are all involved in these things. If you don’t understand these things, it’s very easy Difficulty understanding these grammatical features.

2. How to understand these limitations

This is a big topic, let's summarize it in one sentence: all grammar is for semantics . After saying this, you may still not understand anything. First of all, what is the meaning of semantics? Don’t worry. We will keep repeating this sentence later. Looking back at the end, you will definitely have a deeper understanding.

Let’s step into the details, first let’s take a look at how the compiler saves these restrictions. The compiler corresponds to an entry for each variable, which is probably like this: (just for example, don’t be serious)

item variable name memory address whether const Whether it is volatile lvalue or rvalue reference type
example

a

0x100001

whether

whether lvalue/rvalue non/&/&&

Notice:

1. The value of the variable can be read through the memory address, but not written into it

2. In fact, all entries are lvalues, and rvalues ​​are only a temporary form during the assignment process, but for convenience, they are written inside

In this way, there is the following picture: (I believe you can understand without explaining)

By permutation and combination, we can easily get how many limited situations there are: 2x2x3 + 2x2x1 = 16. (References can only be lvalues, so they are not fully combined)

Let’s write down these 16 restrictions:

 50   int a = 1;
 51 
 52   int t1 = a;
 53   int &t2 = a;
 54   int &&t3 = std::move(a);
 55   2; //t4
 56   volatile int t5 = a;
 57   volatile int &t6 = a;
 58   volatile int &&t7 = std::move(a);
 59   std::move(t5); //t8
 60   const int t9 = a;
 61   const int &t10 = a;
 62   const int &&t11 = std::move(a);
 63   std::move(t9); //t12
 64   const volatile int t13 = a;
 65   const volatile int &t14 = a;
 66   const volatile int &&t15 = std::move(a);
 67   std::move(t13); //t16
 68   // 不考虑volatile
 69   int tt1 = a;
 70   int &tt2 = a;
 71   int &&tt3 = std::move(a);
 72   2; //tt4
 73   const int tt5= a;
 74   const int &tt6 = a;
 75   const int &&tt7 = std::move(a);
 76   std::move(tt5); //tt8
 77   //再不考虑const
 78   int ttt1 = a;
 79   int &ttt2 = a;
 80   int &&ttt3 = std::move(a);
 81   2; //ttt4

Notice:

1. As mentioned above, an rvalue is only an intermediate result, so there is no such type as an rvalue, only an rvalue reference, and an rvalue reference itself is an lvalue.

2. volatile is inherited from C, and tells the compiler not to optimize registers (note, this sentence is semantics). This is only used by low-level programming, so I won’t discuss it too much here, so there are still 8 types left. .

3. If const is further ignored, there are only 4 types, they are the difference between lvalue/rvalue, lvalue reference/rvalue reference/non-reference.

Maybe you don't know much about std::move(), but before explaining it, let's review it first: pass by value and pass by reference

From a macro point of view, the process of program operation is the process of continuously copying data (on both sides of the = sign, copy construction, actual parameters are passed to formal parameters, function returns, variables are placed in containers, etc.), and most of the type restrictions also occur in the copied In the process, that is, you cannot copy a certain limited variable to another specific type of variable. The most common const variables cannot be given non-const references, lvalue references cannot be bound to rvalues, etc.

It seems to be far away. In fact, what I want to say is that in the process of data copying, we must first distinguish whether to pass by value or pass by reference!

If it is value passing, well, you are an uncle, a rich man, it is difficult to make mistakes.

Because the new data and the source data are just one transaction, it doesn’t matter later, the source data will not be changed, and the limiting conditions of the new data are completely determined by yourself.

From the code point of view, if the left side of the equation is int a = , then the right side can be any variable representing int, or a variable that can be converted to int implicitly, all restrictions will not be affected, and as long as the code is compiled, it will basically not error.

 28   int a = 0;
 29   int b = a;
 30   int c = std::move(a);
 31 
 32   int a2 = 0;
 33   int &d = a2;
 34   int e = d;
 35   int f = std::move(d);
 36 
 37 
 38   const int a3 = 0;
 39   int g = a3;
 40   int h = std::move(a3);

std::move() only works if

references and cv-qualifiers, and the difference between lvalue and rvalue:

 11   int a = 2;
 12   const int b = a;
 13   volatile int c = a;
 14   int &d = a;
 15   const volatile int &e = a;
 16   int &&f = std::move(a);

The running process of the program is the process of continuously copying data. In the code, the most common operation is to copy a value to another value. Although it corresponds to the memory, it is a binary copy, but in the compiler, due to various For various reasons, some properties of this set of binary values ​​are retained. During the copying process, do you want to keep these features? Before there is no type inference, this problem is relatively simple. The new type must be fully defined, so the programmer must specify whether there is some kind of limitation (generally not written, there will be a default value, for example, if there is no const, it will be non-const). const, no reference, it is a value, no rvalue is written, it is an lvalue, etc.). But with type deduction, the problem becomes more complicated, especially when the original value is not the default value and the new value wants to use the default value:

 19   int a = 1;
 20   auto &b = a; //增加引用特性,可以
 21   const auto &c = a; //增加const和引用,可以
 22 
 23   const int d = 2;
 24   auto e = 2; //e是否要保留const属性?不好决策,实际上是没有
 25   e++;
 26   auto &f = d; //f必须保留const属性
 27 //  f++;
 28 
 29   int g = 0;
 30   int &h = g;
 31   auto i = h;// i是否是g的引用,还是对应另一片内存?实际上对应另一片内存
 32   i++;
 33   cout << g << endl;
 35   auto j = std::move(g);
 36 //  int &&k = j; j是左值不是右值

As can be seen from the above example, when auto is deduced as a type, references and cv-qualifiers, as well as lvalue and rvalue are not retained.

To preserve these attributes, decltype(auto) must be used as the type deduction identifier:

 19   int a = 1;
 20   auto &b = a; 
 21   const auto &c = a; 
 22 
 23   const int d = 2;
 24   auto e = d; 
 25   e++;
 26   decltype(auto) e2 = d;
 27 //  e2++; 编译失败,保留了const属性
 28   auto &f = d; 
 29 //  f++;
 30 
 31   int g = 0;
 32   int &h = g;
 33   auto i = h;
 34   i++;
 35   cout << g << endl; //0
 36   decltype(auto) i2 = h;
 37   i2++;
 38   cout << g << endl; //1 保留了引用属性
 39 

Guess you like

Origin blog.csdn.net/cyfcsd/article/details/130066882