序列点和方法链

Jos*_*eld 12 c++ method-chaining sequence-points

以下表达式通常用于演示未定义的未指定行为:

f() + g()
Run Code Online (Sandbox Code Playgroud)

如果f()g()都有一些共享对象的副作用,则行为是不确定的不确定,因为执行的顺序是未知的.f()可以在之前评估,g()反之亦然.

现在我想知道当你在一个对象上链接成员函数时会发生什么.比方说,我有一个类,叫做实例的实例obj,它有两个成员函数,foo()bar()这两个修改的对象.这些函数的执行顺序不是可交换的.在一个接一个之前调用一个的效果与用另一个方式调用它们的效果不同.这两个方法都返回一个引用,*this以便它们可以像这样链接:

obj.foo().bar()
Run Code Online (Sandbox Code Playgroud)

但这是不明确的行为吗?我在标准中找不到任何东西(不可否认只是扫描),区分了这个表达式和我在帖子顶部给出的表达.两个函数调用都是full-expression的子表达式,因此它们的执行顺序是未指定的.但肯定foo() 必须先评估,以便bar()知道要修改哪个对象.

也许我错过了一些明显的东西,但我无法看到序列点的创建位置.

Naw*_*waz 10

f() + g()
Run Code Online (Sandbox Code Playgroud)

这里的行为是未指定(未未定义),因为其中各操作数中的评估顺序(即,每个函数被调用)是未指定的.

 obj.foo().bar();
Run Code Online (Sandbox Code Playgroud)

这在C++中是明确定义的.

C++ ISO标准的相关章节§1.9.17,

在调用函数时(无论函数是否为内联函数),在评估函数体中任何表达式或语句之前发生的所有函数参数(如果有) 之后,都会有 一个序列点.在复制返回值之后和执行函数外部的任何表达式之前,还有一个 序列点.

在这些主题中已经详细讨论了类似的案例:


Joh*_*itb 5

如果f()和g()都对某些共享对象有副作用,则行为未定义,因为执行顺序未知.

这不是真的.函数调用不进行交错,在进入函数之前和离开函数之前有一个序列点.g各副作用中的所有副作用f被至少一个序列点分开.行为未定义.

结果,函数的执行顺序fg没有确定,但是一旦执行了一个函数,只执行该函数的评估,而另一个函数"必须等待".不同的可观察结果是可能的,但这并不意味着发生了未定义的行为.

现在我想知道当你在一个对象上链接成员函数时会发生什么.

如果你有,obj.foo().bar()那么你需要首先评估obj.foo()你知道你调用函数的对象bar,这意味着你必须等待obj.foo()返回并产生一个值.然而,这并不一定意味着评估所产生的所有副作用obj.foo()都已完成.在评估表达式后,您需要一个序列点来将这些副作用视为完整.因为在返回之前obj.foo()和调用之前都有一个序列点bar(),所以你实际上有一个确定的顺序来执行通过分别在foo和中bar计算表达式而启动的副作用.

要解释一点,原因foo是之前调用bar在你的例子是一样的,为什么i++功能之前,首先加f叫下面.

int i = 0;
void f() {
  std::cout << i << std::endl;
}

typedef void (*fptype)();
fptype fs[] = { f };

int main() {
  fs[i++]();
}
Run Code Online (Sandbox Code Playgroud)

这里要问的问题是:这个程序是打印的0,1还是未定义或未指定的行为?答案是,因为fs[i++]必须首先在函数调用之前计算表达式,并且在输入之前有一个序列点f,iinside 的值f1.

我认为你不需要将隐式对象参数的范围扩展到序列点来解释你的情况,你当然不能扩展它来解释这种情况(我希望是定义的行为).


C++ 0x草案(不再有序列点)有一个更明确的措辞(强调我的)

当调用函数时(无论函数是否为内联函数),与任何参数表达式相关联的每个值计算和副作用,或者使用指定被调用函数的后缀表达式,都会在执行每个表达式或语句之前对其进行排序.叫功能.