方法链中的 C + + 执行顺序

这个程序的输出:

#include <iostream>
class c1
{
public:
c1& meth1(int* ar) {
std::cout << "method 1" << std::endl;
*ar = 1;
return *this;
}
void meth2(int ar)
{
std::cout << "method 2:"<< ar << std::endl;
}
};


int main()
{
c1 c;
int nu = 0;
c.meth1(&nu).meth2(nu);
}

是:

method 1
method 2:0

为什么 meth2()开始时 nu不是1?

14583 次浏览

因为评估顺序未指定。

你看到 main中的 numeth1被调用之前被评估为 0。这就是锁链的问题。我建议不要这样做。

只要制作一个漂亮、简单、清晰、易读、易懂的程序:

int main()
{
c1 c;
int nu = 0;
c.meth1(&nu);
c.meth2(nu);
}

我认为 标准草案中关于评估顺序的这一部分是相关的:

1.9程序执行

...

  1. 除特别说明外,计算单个运算符的操作数和单个运算符的子表达式 表达式的操作数的值计算 在运算符结果的值计算之前对运算符进行排序。 如果对标量有副作用 对象相对于同一标量对象上的另一个副作用或值计算而言是未排序的 using the value of the same scalar object, and they are not potentially concurrent, the behavior is 未定义

还有:

5.2.2函数调用

...

  1. [注意: < strong > 后缀表达式和参数的计算结果相对于1都是未排序的 在输入函数之前,参数评估的所有副作用都被排序ーー结束注释]

So for your line c.meth1(&nu).meth2(nu);, consider what is happening in operator in terms of the function call operator for the final call to meth2, so we clearly see the breakdown into the postfix expression and argument nu:

operator()(c.meth1(&nu).meth2, nu);

The evaluations of the postfix expression and argument for the final function call (i.e. the postfix expression c.meth1(&nu).meth2 and nu) are 相对于彼此没有序列 as per the 函数调用 rule above. Therefore, the side-effect of the computation of the postfix expression on the scalar object ar is unsequenced relative to the argument evaluation of nu prior to the meth2 function call. By the 程序执行 rule above, this is undefined behaviour.

换句话说,在 meth1调用之后,编译器不需要对 meth2调用的 nu参数进行计算——可以自由地假设 meth1的副作用不会影响 nu的计算。

上面生成的汇编代码在 main函数中包含以下序列:

  1. 变量 nu在堆栈上分配并用0初始化。
  2. 寄存器(在我的例子中是 ebx)接收 nu值的副本
  3. nuc的地址加载到参数寄存器中
  4. meth1被称为
  5. 返回值寄存器和 ebx寄存器中 nu之前缓存的值被加载到参数寄存器中
  6. meth2被称为

重要的是,在上面的步骤5中,编译器允许在对 meth2的函数调用中重用步骤2中的 nu缓存值。这里它忽略了 nu可能已经被 meth1调用改变的可能性-“未定义的行为”在行动中。

注意: 这个答案已经从原来的形式发生了实质性的改变。我最初关于操作数计算在最终函数调用之前没有排序的副作用的解释是不正确的,因为它们是错误的。问题在于操作数本身的计算是不确定的。

我认为在编译函数 meth1和 meth2之前,参数已经传递给它们了。我是说当你用“ c.meth1(& nu)”的时候。Meth2(nu) ;”值 nu = 0已经传递给 meth2,所以后面是否更改“ nu”并不重要。

你可以试试这个:

#include <iostream>
class c1
{
public:
c1& meth1(int* ar) {
std::cout << "method 1" << std::endl;
*ar = 1;
return *this;
}
void meth2(int* ar)
{
std::cout << "method 2:" << *ar << std::endl;
}
};


int main()
{
c1 c;
int nu = 0;
c.meth1(&nu).meth2(&nu);
getchar();
}

it will get the answer you want

在1998年的 C + + 标准中,第5节,第4段

除特别说明外,各个运算符的操作数和各个表达式的子表达式的求值顺序以及副作用发生的顺序未指定。之间 下一个序列点标量对象的存储值应该通过表达式的求值最多修改一次。此外,访问先前的值只是为了确定要存储的值。对于一个完整表达式的子表达式的每一个允许的顺序,应满足本段的要求; 否则行为是未定义的。

(我省略了与本问题无关的脚注 # 53)。

基本上,&nu必须在调用 c1::meth1()之前进行计算,而 nu必须在调用 c1::meth2()之前进行计算。但是,没有要求在 &nu之前计算 nu(例如,允许先计算 nu,然后计算 &nu,然后调用 c1::meth1()——这可能是编译器正在做的事情)。因此,c1::meth1()中的表达 *ar = 1不能保证在 c1::meth1()2中的 nu被计算之前被计算,以便传递给 c1::meth2()

以后的 C + + 标准(我今晚使用的 PC 上目前还没有)基本上都有相同的条款。

这个问题的答案取决于 C + + 标准。

自从 C + + 17以来,规则发生了变化,P0145被接受进入了规范。 Since C++17 the order of evaluation is defined and parameter evaluation would be performed according to the order of the function calls. Note that parameter evaluation order inside a single function call is still not specified.

因此,自 C + + 17以来,链式表达式中的求值顺序保证按照链式表达式的实际顺序运行: 自 C + + 17以来,有关代码的打印顺序保证:

method 1
method 2:1

在 C + + 17之前,它可以打印以上内容,但也可以打印:

method 1
method 2:0

参见: