返回信息流最近在学运算符重载,看到书上有这样一段描述:
> `district2 = sid + sara;`
The compiler, recognizing the operands as belonging to the `Salesperson` class, replaces the operator with the corresponding operator function:
`district2 = sid.operator+(sara);`
原来重载的运算符`+`会被编译器替换成`operator+()`,那么我就打算试验一下。
以下是我的思考过程:
> 如果`+`会被编译器替换成`operator+()`,那么相同的道理应当对于所有双目运算符都成立,比如赋值运算符`=`会被替换成`operator=()`。
而赋值运算符有个特点:在C++17以后的标准中,`E1=E2;`的运算顺序是`E2`在`E1`之前。这点烦请参见[Order of Evaluation](https://en.cppreference.com/w/cpp/language/eval_order)的"Sequenced before" rules第20条:
> > In every simple assignment expression `E1 = E2` and every compound assignment expression `E1 @= E2`, every value computation and side effect of `E2` is sequenced before every value computation and side effect of `E1`.
> 但`E1.operator=(E2);`的运算顺序应该是`E1`在`E2`之前。因为首先要知道是谁在调用`operator=()`成员函数,所以会先把`E1`计算出来。
于是这两种写法在“先算哪个操作数”的问题上存在矛盾。`E1=E2;`会先算`E2`,而`E1.operator=(E2);`会先算`E1`。
如果编译器真的把`=`替换成了`operator=()`,那么在`E1=E2`中也会先算`E1`,后算`E2`。
基于上面的思考过程,我设计了一个简单的实验。代码如下:
```C++
#include<iostream>
using namespace std;
class Com{
public:
Com(int number){
m_number=number;
}
Com &operator=(Com &c){
cout<<"The operator=() called by object "<<this<<"."<<endl;
m_number=c.m_number;
return *this;
}
private:
int m_number;
};
Com &func(Com &c){
cout<<"The func() called by object "<<&c<<"."<<endl;
return c;
}
int main(){
Com a(3),b(5);
cout<<"The address of a is "<<&a<<endl;
cout<<"The address of b is "<<&b<<endl<<endl;
func(a).operator=(func(b));
cout<<endl;
func(a)=func(b);
return 0;
}
```
在[Coliru](https://coliru.stacked-crooked.com/)用GCC 13.1 (C++20)运行,得到的结果是:
```
The address of a is 0x7ffec38a12b8
The address of b is 0x7ffec38a12bc
The func() called by object 0x7ffec38a12b8.
The func() called by object 0x7ffec38a12bc.
The operator=() called by object 0x7ffec38a12b8.
The func() called by object 0x7ffec38a12bc.
The func() called by object 0x7ffec38a12b8.
The operator=() called by object 0x7ffec38a12b8.
```
从代码中可以看出,在调用`func(a).operator=(func(b));`的时候,先运算的是`func(a)`;而在调用`func(a)=func(b)`的时候,先运算的是`func(b)`。这个结果好像与预期并不相符。
我又思考了一下,背后可能的原因有如下3种:
1. 大前提就错了,`E1=E2;`并不会被替换成`E1.operator=(E2);`,于是后面的推断都是空穴来风。
2. `E1=E2;`并不保证`E2`先算出来,我所看到的只是巧合的结果。(Undefined Behaviour)
3. `E1.operator=(E2);`并不保证`E1`先算出来,我所看到的只是巧合的结果。(Undefined Behaviour)
到底是哪一种原因,还是说是另有原因,我不能解释。
所以来问各位大神,希望指点一二。
这是一条镜像帖。来源:北邮人论坛 / cpp / #102829同步于 2023/8/19
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖
【问题】关于“重载运算符”的调用
cppHusky
2023/8/19镜像同步24 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
【 在 bydgm11 的大作中提到: 】
: 猜测是.和()的计算顺序是从左至右计算?
看了一下,没有找到关于`.`运算顺序方面的明文规定。
不过仅仅从感觉上讲`E1.E2`应该是先算`E1`再算`E2`。
否则像这样的代码,就不知道到底要输出什么了:
```C++
class A{
public:
A *pself(){
return this;
}
}
A &func(A &a){
return a;
}
int main(){
A a;
cout<<func(a).pself();
}
```
(如果先运行`pself()`而非`func(a)`,`pself()`就不知道要返回什么了)
【 在 cppHusky 的大作中提到: 】
:
: [md]
: 看了一下,没有找到关于`.`运算顺序方面的明文规定。
: ...................
说起来,[Order of Evaluation](https://en.cppreference.com/w/cpp/language/eval_order)第18条里倒是有一个
> In a pointer-to-member expression `E1.*E2` or `E1->*E2`, every value computation and side effect of `E1` is sequenced before every value computation and side effect of `E2` (unless the dynamic type of `E1` does not contain the member to which `E2` refers).
但这显然不是我想找的信息。说来也怪,连`E1.*E2`都规定顺序了,为啥`E1.E2`没规定顺序?(不解)
binary expr有操作符优先级,而且有left associate以及right ,associate的区别。类似于加减乘除四则运算中,乘除法的优先级高于加减法。
来自chatgpt : 在C++中,函数调用的优先级高于赋值运算符。因此,在表达式 (func(a)).operator=(func(b)) 中,首先计算 func(a) 和 func(b),然后调用 operator= 函数。在表达式 func(a)=func(b) 中,由于赋值运算符的结合性是从右到左,所以首先计算 func(b),然后计算 func(a),最后进行赋值操作。
【 在 ll402 的大作中提到: 】
: 赋值运算符和重载赋值不能归为一类叭?如果把重载运算符当做一个普通的方法来看,那就应该是先找参数了。
我们假设,重载的`=`相当于`operator=()`这个“方法”,
那么`E1=E2`应当与`E1.operator=(E2)`的结果完全一致才对吧?
可是现在的情况并非如此。
`E1=E2`是先找`E2`(这点与原本的赋值运算符一致),而`E1.operator=(E2)`是先找`E1`了。
【 在 a1009 的大作中提到: 】
: 来自chatgpt : 在C++中,函数调用的优先级高于赋值运算符。因此,在表达式 (func(a)).operator=(func(b)) 中,首先计算 func(a) 和 func(b),然后调用 operator= 函数。在表达式 func(a)=func(b) 中,由于赋值运算符的结合性是从右到左,所以首先计算 func(b),然后计算 func(a),最后进行赋值操作。
恕我不敢苟同。
GPT的回答中指出`func(a)`和`func(b)`的调用在`operator=()`之前,这并无错误。然而,我想要明确问题之一是**`func(a)`和`func(b)`的调用顺序**。此言答非所问。
另外,关于赋值运算符的结合性……其实结合性和操作数(operand)的运算顺序没什么关系(事实上它决定的是运算符(operator)的运算顺序),`E1=E2;`这里先算`E2`后算`E1`不是因为`=`结合性right to left,而是C++ 17起的标准中有明确规定:
> In every simple assignment expression `E1 = E2` and every compound assignment expression `E1 @= E2`, every value computation and side effect of `E2` is sequenced before every value computation and side effect of `E1`.
(小声嘀咕:所以我一般是不敢用ChatGPT来问语法问题的)