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

时间:2016-05-16 11:00:21

标签: c++ chaining operator-precedence

该计划的输出:

#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

nu启动时为什么meth2()不是1?

5 个答案:

答案 0 :(得分:63)

因为未指定评估顺序。

即使nu被调用,您main中的0被评估为meth1。这是链接的问题。我建议不要这样做。

制作一个简单,清晰,易读,易于理解的程序:

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

答案 1 :(得分:27)

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

  

1.9程序执行

     

...

     
      
  1. 除非另有说明,否则评估个体操作员的操作数和个体的子表达式   表达式没有排序。一个操作数的值计算   在运算符结果的值计算之前对运算符进行排序。 如果对标量有副作用   对于相同标量对象的另一个副作用或值计算,对象未被排序   使用相同标量对象的值,并且它们不可能并发,行为是   未定义
  2.   

还有:

  

5.2.2函数调用

     

...

     
      
  1. [注意:对后缀表达式和参数的评估都是相对于一个没有排序的   另一个。在输入函数之前,参数评估的所有副作用都会被排序 - 结束注释]
  2.   

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

nu

最终函数调用的后缀表达式和参数的评估(即后缀表达式operator()(c.meth1(&nu).meth2, nu); c.meth1(&nu).meth2相对于彼此无序< / em>根据上面的函数调用规则。因此,相对于nu之前的ar的参数评估,标量对象nu上的后缀表达式计算的副作用未被排序功能调用。通过上面的程序执行规则,这是未定义的行为。

换句话说,在meth2调用之后,编译器无需评估nu调用的meth2参数 - 可以自由地假设没有副作用meth1会影响meth1评估。

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

  1. 变量main在堆栈上分配,并用0初始化。
  2. 寄存器(在我的情况下为nu)会收到ebx
  3. 值的副本
  4. nunu的地址已加载到参数寄存器
  5. c被称为
  6. 返回值寄存器和meth1寄存器中nu以前缓存的值被加载到参数寄存器中
  7. ebx被称为
  8. 重要的是,在上面的步骤5中,编译器允许在对meth2的函数调用中重用步骤2中的缓存值nu。这里忽略了meth2可能因调用nu - “未定义行为”而发生变化的可能性。

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

答案 2 :(得分:9)

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

  

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

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

基本上,&nu必须在调用c1::meth1()之前进行评估,并且必须在调用nu之前评估c1::meth2()。但是,并不要求在nu之前评估&nu(例如,允许首先评估nu,然后&nu,然后c1::meth1()被调用 - 这可能是你的编译器正在做的事情。因此,*ar = 1中的c1::meth1()中的nu表达式无法保证在评估main()中的c1::meth2()之前进行评估,以便传递给System.Web.Mail

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

答案 3 :(得分:6)

我认为在编译时,在真正调用函数meth1和meth2之前,参数已经传递给它们。我的意思是当你使用“c.meth1(&amp; 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();
}

它会得到你想要的答案

答案 4 :(得分:2)

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

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

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

method 1
method 2:1

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

method 1
method 2:0

另见: