C++表达式求值中构造函数与运算符重载的调用顺序问题
核心规则
这两种输出都是完全合规的,根本原因是:C++对普通函数调用(包括用户自定义的运算符重载)的多参数求值顺序,从来没有强制要求从左到右执行,编译器可以根据优化需求自由安排求值顺序。
先把这行代码的编译逻辑拆明白
针对THING u = (t + 3) + 5;这行:
- 你的
THING类有带int参数的构造函数,编译器会自动把字面量3、5隐式转成THING类型的临时对象 - 代码里的两次
+都不是内置加法,是调用你写的友元operator+,本质和两次普通函数调用没有区别:- 第一次调用:
operator+(t, THING(3)),返回一个临时对象 - 第二次调用:
operator+(第一次调用返回的临时对象, THING(5)),用返回值初始化u
- 第一次调用:
- 这里是定义时的拷贝初始化,不是赋值,所以根本不会触发
operator=的打印逻辑。
两种输出分别是怎么跑出来的
C++标准对函数调用的要求只有一条:调用函数之前,所有传入的参数必须求值完成。但标准从来没规定多个参数之间要先算哪个、后算哪个,只要最后给函数传的参数值没错,怎么安排顺序都行。
对应到这行代码的两种合法执行路径:
- 输出
3 -> 3 5 -> 15的流程:- 按从左到右的顺序走:先构造3对应的临时THING(打印
3),调用内层的operator+(打印->),生成x=3的临时结果(打印3) - 再构造5对应的临时THING(打印
5) - 两个参数都齐了,调用外层的operator+(打印
->),生成最终x=15的u对象(打印15)
- 按从左到右的顺序走:先构造3对应的临时THING(打印
- 输出
5 3 -> 3 -> 15的流程:- 编译器直接调整了参数求值顺序:先把外层函数的右参数——也就是5对应的临时THING提前构造好(打印
5) - 再去算外层函数的左参数:也就是内层的
t+3,先构造3对应的临时THING(打印3),调用内层的operator+(打印->),生成x=3的临时结果(打印3) - 两个参数都齐了,调用外层的operator+(打印
->),生成最终x=15的u对象(打印15)
- 编译器直接调整了参数求值顺序:先把外层函数的右参数——也就是5对应的临时THING提前构造好(打印
容易搞混的点
- 别被“C17改了求值顺序”的说法带偏:C17只对内置运算符、特殊运算符(比如
.、->、后置自增)的顺序做了强制约束,用户自定义的运算符本质就是普通函数,参数求值顺序在所有C++标准里都是未指定的,所以两种输出哪怕在最新的C++版本下依然是合法的。 - 你注释里说的“不会走赋值、调用拷贝构造”是对的:现在编译器默认开返回值优化(RVO),会直接把operator+返回的对象构造到u的内存位置,省掉中间的拷贝步骤,而默认生成的拷贝构造本来就没有打印逻辑,自然看不到额外输出。
补充:这种可以由编译器自由选择执行顺序的情况属于未指定行为,不是未定义行为。不管选哪种路径,最终计算出来的x值都是完全正确的,不会出现程序崩溃、值乱改的问题,只是打印的先后顺序会有区别。
内容的提问来源于stack exchange,提问作者froest
相关产品推荐
相关产品推荐

