15

我已经调试了一段时间的程序,最终发现错误是由于引用没有像我想象的那样更新。

这是一个显示我遇到的问题的示例:

#include <iostream>
using namespace std;

struct Test {
    Test& set(int& i){ i = 10; return *this; }
    Test& print(const int& i){ cout << i << endl; return *this; }
};

int main(void){
    int i = 0;
    Test t;

    t.set(i).print(i + 5);

    return 0;
}

我原以为这里的 print() 方法会输出 15,但结果却是 5。

编辑: 10 天后,我才意识到用 clang 输出 15!这是 GCC 中的错误吗?

4

3 回答 3

10

让我试着解释一下 C++11 标准。在 §1.9/15 中它说:

除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的求值是无序的。[...] 如果标量对象的副作用相对于同一标量对象的另一个副作用或使用同一标量对象的值的值计算是无序的,则行为未定义。

当然int是一个标量类型,并且包含对in和 value 计算t.set(i).print(i + 5);的副作用,所以如果没有另外说明,行为确实是未定义的。阅读第 5.2.5 节(“类成员访问”),我找不到任何关于运算符序列的注释。[但请参阅下面的编辑!]iset()i + 5.

但是请注意,当然可以保证set()在之前执行,print()因为后者将前者的返回值作为(隐式this)参数接收。这里的罪魁祸首是 的参数的值计算print未排序的相对于set.

编辑:在阅读了您(@Xeno's)评论中的答案后,我重新阅读了标准中的段落,实际上它后来说:

调用函数(包括其他函数调用)中的每个求值,如果在被调用函数的主体执行之前或之后没有特别排序,则相对于被调用函数的执行是不确定的。

因为不确定排序不是未排序的(“未排序评估的执行可以重叠”, §1.9 /13),这确实不是未定义的行为,而是“只是”未指定的行为,这意味着 15 和 5 都是正确的输出。

因此,当<表示“之前排序”并~表示“不确定排序”时,我们有:

(value computations for print()'s arguments ~ execution of set()) < execution of print()

于 2014-02-16T13:09:36.663 回答
2

C++ 中不能保证单个表达式中的函数参数的计算顺序,即使这些函数是链接的方法调用也是如此。你在这里调用未定义的行为,这就是你得到的。

这 。运算符确实暗示了排序,但仅限于 . 之前的表达式。在访问成员之前必须对其进行全面评估。这并不意味着子表达式的评估在此之前被暂停。

此外,不要通过ints const int&,这不可能比int直接传递更快(除非出于某种奇怪的原因int不适合处理器字并且引用可以)。

于 2014-02-16T11:38:22.653 回答
0

[太长的评论:]

如果添加

Test& add(int& i, const int toadd)
{
  i += toadd;
  return *this;
}

这个电话

t.set(i).add(i, 5).print(i);

返回

15

由此我得出结论,罪魁祸首是 i + 5要打印的 as 参数。

于 2014-02-16T13:17:42.413 回答