序列点和方法链接

发布于 2024-10-30 00:17:28 字数 653 浏览 13 评论 0原文

以下表达式通常用于演示 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

思念满溢 2024-11-06 00:17:28
f() + g()

这里的行为是未指定(不是未定义),因为计算每个操作数(即调用每个函数)的顺序未指定 >。

 obj.foo().bar();

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

C++ ISO 标准的相关章节 §1.9.17 如下:

当调用函数时(无论是
不是内联函数),有
评估后的序列点
所有函数参数(如果有)
这发生在执行之前
中的任何表达或陈述
函数体
。还有一个
复制后的序列点
返回值和之前
执行外部的任何表达式
函数


在以下主题中已详细讨论了类似的情况:

f() + g()

Here the behavior is unspecified (not undefined), because the order in which each operand is evaluated (that is, each function is called) is unspecified.

 obj.foo().bar();

This is well-defined in C++.

The relevant section §1.9.17 from the C++ ISO standard reads,

When calling a function (whether or
not the function is inline), there is
a sequence point after the evaluation
of all function arguments
(if any)
which takes place before execution of
any expressions or statements in the
function body
. There is also a
sequence point after the copying of a
returned value and before the
execution of any expressions outside
the function
.

Similar cases has been discussed in great detail, in these topics:

高速公鹿 2024-11-06 00:17:28

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

这不是真的。函数调用不会交错,并且在进入函数之前和离开函数之前都有一个序列点。 g 中的所有副作用与 f 中的副作用相关,至少由一个序列点分隔。行为并非未定义。

因此,函数 fg 的执行顺序不确定,但一旦执行一个函数,则仅执行该函数的计算,而另一个函数则执行“必须等待”。不同的可观察结果是可能的,但这并不意味着发生了未定义的行为。

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

如果您有 obj.foo().bar() 那么您需要首先评估 obj.foo() 以了解您调用函数 bar 的对象code> 开启,这意味着您必须等待 obj.foo() 返回并产生一个值。然而,这并不一定意味着由 obj.foo() 计算引发的所有副作用都已完成。计算表达式后,您需要一个序列点才能将这些副作用视为完成。因为在从 obj.foo() 返回之前以及调用 bar() 之前有一个序列点,所以您实际上已经确定了执行由评估表达式引发的副作用的顺序分别在 foobar 中。

为了多解释一下,在您的示例中,在 bar 之前调用 foo 的原因与为什么 i++ 在函数 之前首先递增的原因相同。 code>f 在下面被调用。

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

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

int main() {
  fs[i++]();
}

这里要问的问题是:这个程序会打印01还是它的行为未定义或未指定?答案是,因为表达式fs[i++]在函数调用之前必然要先求值,并且在进入f之前有一个序列点,<的值f 内的 code>i 是 1

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


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

调用函数时(无论该函数是否内联),与任何参数表达式或与指定被调用函数的后缀表达式相关联的每个值计算和副作用都会在执行前进行排序被调用函数体内的每个表达式或语句。

If f() and g() both have side effects on some shared object then the behaviour is undefined because the order of execution is unknown.

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 in f are separated by at least one sequence point. Behavior is not undefined.

As a consequence, the order of execution of the functions f and g 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.

Now I was wondering what happens when you chain member functions on an object.

If you have obj.foo().bar() then you need to first evaluate obj.foo() to know what object you call function bar on, which means you have to wait for obj.foo() to return and yield a value. This however does not necessarily mean that all side effects initiated by evaluation of obj.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 from obj.foo() and also before calling bar(), you have effectively a determined order for executing side effects initiated by evaluating expressions in foo and bar respectively.

To explain a bit more, the reason foo is called before bar in your example is the same to why i++ is first incremented before the function f is called in the following.

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

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

int main() {
  fs[i++]();
}

The question to ask here is: Will this program print 0, 1 or is its behavior undefined or unspecified? The answer is, because the expression fs[i++] necessarily has to be first evaluated before the function call, and there is a sequence point before entering f, the value of i inside f is 1.

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)

When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function.

奈何桥上唱咆哮 2024-11-06 00:17:28

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文