使用C++ 11,编写f(x ++),g(x ++)是不确定的行为?

ein*_*ica 42 c++ expression undefined-behavior c++11

我正在读这个问题:

未定义的行为和序列点

并且,特别是C++ 11的答案,我理解评估的"排序"的想法.但是 - 写作时是否有足够的顺序:

f(x++), g(x++);

也就是说,我保证f()获得原始值xg()获得一次递增x

挑剔的注意事项:

  • 假设operator++()已定义的行为(即使我们已经重写它),这样做f()g(),没有异常将被抛出,等-这个问题是不是有关.
  • 假设operator,()没有超载.

Sto*_*ica 49

不,行为已定义.引用C++ 11(n3337)[expr.comma/1]:

用逗号分隔的一对表达式从左到右进行评估; 左表达式是一个废弃值表达式(Clause [expr]). 在与右表达式相关联的每个值计算和副作用之前,对与左表达式相关联的每个值计算和副作用进行排序.

我把"每一个"都称为"每一个" 1.x++在呼叫序列f完成并f返回之前,不能进行第二次评估.2


1析构函数调用与子表达式无关,仅与完整表达式相关联.因此,您将看到在完整表达式结束时以相反顺序执行的临时对象创建.
2本段仅适用于作为运营商使用的逗号.当逗号具有特殊含义时(例如在指定函数调用参数序列时),这不适用.

  • @StoryTeller:对不起,好的,谢谢,这绝对违反直觉:*"销毁临时对象的价值计算和副作用只与全表达有关,而与任何特定的子表达无关."*绝对不是我所期望的从逻辑上讲,析构函数调用表达式*与它相关联.我建议在你的回答中添加析构函数调用,因为它会向那些没有阅读标准的人提出建议...... (2认同)

Som*_*ude 23

不,它不是未定义的行为.

根据此评估顺序和排序参考,逗号的左侧在右侧之前完全评估(参见规则 9):

9)内置逗号运算符的第一个(左)参数的每个值计算和副作用在每个值计算和第二个(右)参数的副作用之前排序.

这意味着像的表达f(x++), g(x++)未定义.

请注意,这仅对内置逗号运算符有效.

  • 并且,为了强调,这不是C++ 11独有的.自远古以来就是如此. (12认同)

Ded*_*tor 12

这取决于.

首先,让我们假设它x++本身不会调用未定义的行为.考虑有符号溢出,递增过去的结束指针,或者postfix-increment-operator可能是用户定义的).
此外,让我们假设调用f()g()使用他们的参数并销毁临时对象不会调用未定义的行为.
这是相当多的假设,但如果它们被打破,答案是微不足道的.

现在,如果逗号是内置的逗号操作符,逗号的支撑,初始化列表,或在MEM-初始化列表中的逗号,左侧和右侧分别之前或之后彼此测序(和你知道哪个),所以不要干涉,使行为定义明确.

struct X {
    int f, g;
    explicit X(int x) : f(x++), g(x++) {}
};
// Demonstrate that the order depends on member-order, not initializer-order:
struct Y {
    int g, f;
    explicit Y(int x) : f(x++), g(x++) {}
};
int y[] = { f(x++), g(x++) };
Run Code Online (Sandbox Code Playgroud)

否则,如果x++为postfix-increment调用用户定义的operator-overload,则会对两个实例进行不确定的排序,x++从而对未指定的行为进行排序.

std::list<int> list{1,2,3,4,5,6,7};
auto x = begin(list);
using T = decltype(x);

void h(T, T);
h(f(x++), g(x++));
struct X {
    X(T, T) {}
}
X(f(x++), g(x++));
Run Code Online (Sandbox Code Playgroud)

在最后一种情况下,你得到了完整的未定义行为,因为两个后缀增量x是未经测序的.

int x = 0;

void h(int, int);
h(f(x++), g(x++));
struct X {
    X(int, int) {}
}
X(f(x++), g(x++));
Run Code Online (Sandbox Code Playgroud)