The principle of C++ self-increment/decrement operator and the essential difference between prefix and suffix forms

The principle of C++ self-increment/decrement operator and the essential difference between its prefix and suffix forms

First come to the conclusion :

  1. The essence of the suffix increment operator is to copy the operation object to generate a copy object, then increment the original operation object by 1, and finally return the copy object. Therefore, postfix increment returns an rvalue. The principle can be simulated by the following function:

    const Type function_postfix_increment(Type& obj){
          
          
    	Type copyObj = obj;
    	obj += 1; // 假定Type支持+=整数的操作
    	return copyObj;
    }
    
  2. The prefix auto-increment operator directly increments the operand by 1, and then returns directly, so there is no copy operation. Therefore, prefix increment returns an lvalue. The principle can be simulated by the following function:

    Type& function_prefix_increment(Type& obj){
          
          
    	obj += 1; // 假定Type支持+=整数的操作
    	return obj;
    }
    
  3. The principle of the pre/postfix decrement operator is similar;

  4. Because of this, generally do not use suffix auto-increment if you can use prefix auto-increment, because for complex objects, the copy overhead of suffix auto-increment operator cannot be ignored;

  5. It is worth mentioning that the priority of suffix increment/decrement is higher than that of prefix increment/decrement.

Valuable content ends here, and the rest is full of water .


The knowledge in this article comes from "C++ Primer (5th Edition)". Strictly speaking, there is no original insight. However, the author has seen many articles on the Internet. Most of their explanations for the suffix auto-increment operator are as follows: "First calculate the expression , and then auto-increment the object that the operator acts on ", this understanding does not seem to cause any problems. (You may think, since you say so, is it possible that this understanding is problematic? Question.jpg)

Don't worry, let's look at an example first, please tell me the output of the following program:

int x = 3;
std::cout << x++ * x++;

Is there a result, is it 9? It seems that the common compiler outputs are all 9.
"Let me just say, x is 3, first calculate the multiplication according to 3, output the value of 3*3, and then x will increase to 5."

What about the following example?

int x = 3;
x = x++;
std::cout << x;

Output 3? Output 4? Is it as you expected? However, on my machine, both results exist. For example, the DevCpp compiler (C++11) outputs 4, while the VS2017 compiler (C++17) outputs 3. In fact, the operations in both examples presented above have undefined behavior . (If you are not sure, add a -Wall after the compilation command to detect undefined behavior)

However, according to logic, these undefined behaviors above do not mean that the above explanation of the postfix auto-increment operator is wrong (or right) - because after all, it is undefined behavior, which is determined by the compiler rather than the C++ standard Determining the behavior, whether its result is as expected, cannot make an assertion about the functionality of the increment operator.

To be honest, in the behavior defined by the C++ standard, I haven't found an example that does not meet the above explanation. However, if the suffix increment operator really works in the way of "complete the calculation of the expression first, and then increment by 1", then you might as well ask yourself: Everyone is an operator, why is the suffix increment operator so "unique " — — In addition to acting on operands, also know when the expression is executed? Could it be that it has such a magic power, that is, without any additional overhead, it can know the time when the expression calculation is completed, and then add 1 to its own value just after that? If there is no overhead, then it's amazing; but if there is overhead (and it's estimated to be not small), then what's the point of this operator?

In fact, the suffix increment operator itself is very aware of the mystery, that is: the operator itself can be regarded as a function of the operand . As for the function of the function, specifically, the suffix increment operator is for the operation The object first performs the Copy operation, then increments the original object by 1, and finally returns the copied object . The above process is written as an overloaded operator function in the following form:

const Type function_postfix_increment(Type& obj){
    
    
	Type copyObj = obj;
	obj += 1; // 假定Type支持+=整数的操作
	return copyObj;
}

This actually explains why the return result of the suffix increment/decrement operator is an rvalue - because it returns a copy object whose life cycle is limited to the function, if it returns a reference (lvalue), it will It will refer to an object that has been released after exiting the function, which is obviously not allowed. Here the return value is modified with const in order to be consistent with the original definition. After all, for an object x, the operation x++++is illegal (because x++ returns an rvalue, it is wrong to write an rvalue).

This also leads to the principle of the prefix auto-increment/decrement operator, that is, directly increments the operand by 1, and finally returns the original object . The above process is written as an overloaded operator function in the following form:

Type& function_prefix_increment(Type& obj){
    
    
	obj += 1; // 假定Type支持+=整数的操作
	return obj;
}

It can be seen that the return value of the prefix increment/decrement operator is an lvalue .

The difference between the two is also reflected in performance. Since the suffix auto-increment operator has a copy process, for more complex types, its overhead cannot be ignored, and may even be very large. However, in general, no distinction is made (the compiler optimizes it).

So far, the working principles and differences of the auto-increment/decrement operators have been discussed.

Finally, give some small conclusions about priority and associativity: postfix increment/decrement operators have higher priority than prefixes; auto increment/decrement operators are combined from right to left.

Finally, give an example of overloading pre/postfix auto-increment/decrement operators, from which you can get a glimpse of the priority and combination.

#include<iostream>
using namespace std;


struct Nint{
    
    
	int x;
	Nint(int _x): x(_x){
    
    
	}
	// 前缀 
	const Nint operator++(int){
    
     
		cout << "postfix ++ on Address " << this << " with value " << x << endl; 
		Nint ret(x);
		x+=1;
		return ret;
	}
	const Nint operator--(int){
    
     
		cout << "postfix -- on Address " << this << " with value " << x << endl; 
		Nint ret(x);
		x-=1;
		return ret;
	}
	// 前缀 
	Nint& operator++(){
    
    
		cout << "prefix ++ on Address " << this << " with value " << x << endl; 
		x+=1;
		return *this;
	}
	Nint& operator--(){
    
    
		cout << "prefix -- on Address " << this << " with value " << x << endl; 
		x-=1;
		return *this;
	}
	void print(){
    
    
		cout << x << endl;
	}
};

int main(){
    
    
	Nint x(7);
	// x++++; // erroneous: (x++)返回const Nint对象,常量对象不能调用非const成员函数 
	// ++++x++; // erroneous: 后缀自增比前缀自增优先级高 
	// &x++; // erroneous: taking address of temporary 
	(++++x)++; // ok
	cout << "Address of x is " << &x << endl;
	x.print();	
	cout << "=========Base Type=========" << endl;
	int a = 7;
	// a++++; // erroneous: lvalue required as increment operand 
	// ++++a++; // same as above
	// &a++; // erroneous: lvalue required as unary '&' operand
	cout << "Address of a is " << &a << endl;
	(++++a)++; // ok
	cout << a << endl;
	return 0;
}

output:

prefix ++ on Address 0x6bfeec with value 7
prefix ++ on Address 0x6bfeec with value 8
postfix ++ on Address 0x6bfeec with value 9
Address of x is 0x6bfeec
10
=========Base Type=========
Address of a is 0x6bfee8
10

From the above examples, we can see that our overloaded operator function is still different from the original definition (the reason for the error is different). In addition, readers can try to remove the const of the return value of the overloaded function of the suffix auto-increment operator to see what changes.

The author's level is limited, if there is any improper description in the article, please criticize and correct~

Guess you like

Origin blog.csdn.net/weixin_42430021/article/details/126858707