序列点和方法链接
以下表达式通常用于演示 undefined 未指定的行为:
f() + g()
如果 f()
和 g()
都对某些共享对象有副作用,则由于执行顺序未知,因此行为未指定。 f()
可以在 g()
之前计算,反之亦然。
现在我想知道当你在一个对象上链接成员函数时会发生什么。假设我有一个类的实例,该实例名为 obj ,它有两个成员函数, foo() 和 bar() ,其中两者都修改对象。这些函数的执行顺序不可交换。在另一个之前调用一个的效果与反过来调用它们的效果不同。这两种方法都返回对 *this
的引用,以便它们可以像这样链接起来:
obj.foo().bar()
但这是未指定的行为吗?我在标准中找不到任何东西(诚然只是浏览)来区分这个表达式和我在帖子顶部给出的表达式。这两个函数调用都是完整表达式的子表达式,因此它们的执行顺序未指定。但肯定必须首先评估 foo()
,以便 bar()
知道要修改哪个对象。
也许我错过了一些明显的东西,但我看不到序列点是在哪里创建的。
The following expression is often used to demonstrate undefined unspecified behaviour:
f() + g()
If f()
and g()
both have side effects on some shared object then the behaviour is undefined unspecified because the order of execution is unknown. f()
may be evaluated before g()
or vice versa.
Now I was wondering what happens when you chain member functions on an object. Let's say I have an instance of a class, the instance called obj
and it has two member functions, foo()
and bar()
which both modify the object. The order of execution of these functions is not commutative. The effect of calling one before the other is not the same effect as calling them the other way around. Both methods return a reference to *this
so that they can be chained like so:
obj.foo().bar()
But is this unspecified behaviour? I can't find anything in the standard (admittedly just scanning through) that differentiates between this expression and the expression I gave at the top of the post. Both function calls are subexpressions of the full-expression and so their order of execution is unspecified. But surely foo()
must be evaluated first so that bar()
knows which object to modify.
Perhaps I'm missing something obvious, but I can't see where a sequence point is created.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这里的行为是未指定(不是未定义),因为计算每个操作数(即调用每个函数)的顺序未指定 >。
这在 C++ 中是明确定义的。
C++ ISO 标准的相关章节 §1.9.17 如下:
在以下主题中已详细讨论了类似的情况:
Here the behavior is unspecified (not undefined), because the order in which each operand is evaluated (that is, each function is called) is unspecified.
This is well-defined in C++.
The relevant section §1.9.17 from the C++ ISO standard reads,
Similar cases has been discussed in great detail, in these topics:
这不是真的。函数调用不会交错,并且在进入函数之前和离开函数之前都有一个序列点。
g
中的所有副作用与f
中的副作用相关,至少由一个序列点分隔。行为并非未定义。因此,函数
f
和g
的执行顺序不确定,但一旦执行一个函数,则仅执行该函数的计算,而另一个函数则执行“必须等待”。不同的可观察结果是可能的,但这并不意味着发生了未定义的行为。如果您有
obj.foo().bar()
那么您需要首先评估obj.foo()
以了解您调用函数bar
的对象code> 开启,这意味着您必须等待 obj.foo() 返回并产生一个值。然而,这并不一定意味着由 obj.foo() 计算引发的所有副作用都已完成。计算表达式后,您需要一个序列点才能将这些副作用视为完成。因为在从 obj.foo() 返回之前以及调用 bar() 之前有一个序列点,所以您实际上已经确定了执行由评估表达式引发的副作用的顺序分别在foo
和bar
中。为了多解释一下,在您的示例中,在
bar
之前调用foo
的原因与为什么i++
在函数之前首先递增的原因相同。 code>f
在下面被调用。这里要问的问题是:这个程序会打印
0
、1
还是它的行为未定义或未指定?答案是,因为表达式fs[i++]
在函数调用之前必然要先求值,并且在进入f
之前有一个序列点,<的值f
内的 code>i 是1
。我认为您不需要将隐式对象参数的范围扩展到序列点来解释您的情况,并且您当然不能扩展它来解释这种情况(我希望这是定义的行为)。
C++0x 草案(不再有序列点)对此有更明确的措辞(强调我的)
This is not true. Function invocations do not interleave, and there is a sequence point before entering functions, and before leaving functions. All side effects in
g
respective to side effects inf
are separated by at least one sequence point. Behavior is not undefined.As a consequence, the order of execution of the functions
f
andg
is not determined, but once one function is executed, only that function's evaluations are executed, and the other function "has to wait". Different observable results are possible, but this does not imply that undefined behavior has happened.If you have
obj.foo().bar()
then you need to first evaluateobj.foo()
to know what object you call functionbar
on, which means you have to wait forobj.foo()
to return and yield a value. This however does not necessarily mean that all side effects initiated by evaluation ofobj.foo()
are finished. After evaluating an expression, you need a sequence point for those side effects to be considered complete. Because there is a sequence point before returning fromobj.foo()
and also before callingbar()
, you have effectively a determined order for executing side effects initiated by evaluating expressions infoo
andbar
respectively.To explain a bit more, the reason
foo
is called beforebar
in your example is the same to whyi++
is first incremented before the functionf
is called in the following.The question to ask here is: Will this program print
0
,1
or is its behavior undefined or unspecified? The answer is, because the expressionfs[i++]
necessarily has to be first evaluated before the function call, and there is a sequence point before enteringf
, the value ofi
insidef
is1
.I don't think you need to extend the scope of the implicit object parameter to sequence points to explain your case, and you certainly cannot extend it to explain this case (which I hope is defined behavior).
The C++0x draft (which doesn't have sequence points anymore) has a more explicit wording of this (emphasize mine)
Foo() 将在 bar() 之前执行。我们必须首先确定 Foo() ,否则我们将不知道 bar() 应该作用于什么(我们甚至不知道 bar() 属于什么类型的类。如果你认为,可能会更直观如果 foo 返回 obj 的新实例,而不是 this,或者如果 foo 返回一个完全不同的类的实例,并且该类也定义了 bar() 方法,
您可以通过在 foo 和 中使用断点来自行测试 。酒吧,看看哪个先被击中。
Foo() will be executed before bar(). We have to determine Foo() first, or else we won't know what bar() should act on ( we wont even know what type of class bar() belongs to. It might be more intuitive to you if you think, what would we have to do if foo returned a new instance of obj, instead of this. Or if foo returned an instance of an entirely different class that also has a bar() method defined.
You can test this yourself by using breakpoints in foo and bar and seeing which gets hit first.