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

C++ execution order in method chaining

这个程序的输出:

#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?

因为未指定评估顺序。

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

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

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

我认为 draft standard 关于评估顺序的这一部分是相关的:

1.9 Program Execution

...

  1. Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, and they are not potentially concurrent, the behavior is undefined

还有:

5.2.2 Function call

...

  1. [ Note: The evaluations of the postfix expression and of the arguments are all unsequenced relative to one another. All side effects of argument evaluations are sequenced before the function is entered — end note ]

因此对于您的行 c.meth1(&nu).meth2(nu);,请考虑在最终调用 meth2 的函数调用运算符方面运算符中发生的情况,因此我们清楚地看到后缀表达式的分解和参数 nu:

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

最终函数调用(即后缀表达式 c.meth1(&nu).meth2nu)的 后缀表达式和参数 的计算结果是 根据上面的 函数调用 规则,相对于另一个 无序。因此,相对于 numeth2函数调用。根据上面的程序执行规则,这是未定义的行为。

换句话说,编译器不需要在 meth1 调用之后评估 meth2 调用的 nu 参数 - 可以自由假设任何一方 - meth1 的影响影响 nu 评估。

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

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

重要的是,在上面的第 5 步中,编译器允许在对 meth2 的函数调用中重新使用来自第 2 步的 nu 的缓存值。在这里它忽略了 nu 可能已经被调用 meth1 - 'undefined behaviour' 改变的可能性。

注意:此答案已从其原始形式进行了实质性更改。我最初关于在最终函数调用之前未对操作数计算的副作用进行排序的解释是不正确的,因为它们是。问题在于操作数本身的计算顺序不确定。

我认为在编译时,在真正调用函数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();
}

它会得到你想要的答案

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

Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified. Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined.

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

本质上,&nu 必须在调用 c1::meth1() 之前求值,而 nu 必须在调用 c1::meth2() 之前求值。但是,没有要求在 &nu 之前评估 nu(例如,允许先评估 nu,然后 &nu,然后 c1::meth1()被称为 - 这可能是您的编译器正在做的事情)。因此,c1::meth1() 中的表达式 *ar = 1 不能保证在 main() 中的 nu 被求值之前求值,以便传递给 c1::meth2().

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

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

自 C++17 以来,规则发生了变化,P0145 被接受到规范中。 自 C++17 起,定义了求值顺序,参数求值将根据函数调用的顺序执行。请注意,单个函数调用中的参数评估顺序仍未指定。

因此,自 C++17 起,链式表达式中的求值顺序得到保证,以链的实际顺序工作:自 C++17 起,有问题的代码被保证打印:

method 1
method 2:1

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

method 1
method 2:0

另请参阅: