【问题标题】:In f(x), can x be evaluated before f?在 f(x) 中,可以在 f 之前计算 x 吗?
【发布时间】:2014-05-16 19:57:16
【问题描述】:

我有一个 C++ 程序。这个程序做了这样的事情:

struct MyT {void memfunc(std::unique_ptr<MyT> arg);};
std::unique_ptr<MyT> obj = /* some init */;
obj->memfunc(std::move(obj));

这是否保证有效,或者我最终可以在 nullptr 上调用成员函数吗?
适用标准报价。
我知道参数的评估顺序是未排序的,但我不记得排序是什么 w.r.t.被调用的函数对象。

【问题讨论】:

  • 我想说这样的代码一旦遇到就应该避免和/或重写。
  • 我们可以(hackily)用MyT *obj2 = obj.get(); obj2-&gt;memfunc(std::move(obj)); 解决这个问题吗?

标签: c++ c++11


【解决方案1】:

Pre-C++17,这是未定义的行为:

这里的引用证明调用函数所需的所有评估和相关的副作用都在函数调用之前排序。
此外,所有其他未明确排序的评估都是不确定排序的。
这不会对评估的子表达式施加任何排序约束,尽管它们彼此之间保持无序

1.9 程序执行§ 15

除非另有说明, 单个运算符的操作数和单个表达式的子表达式的计算是无序的。
[...]
运算符的操作数的值计算在运算符结果的值计算之前排序。如果标量对象上的副作用相对于同一标量对象的另一个副作用或使用同一标量对象的值的值计算未排序,则行为未定义。
调用函数时(无论函数是否内联),与任何参数表达式或与指定被调用函数的后缀表达式相关的每个值计算和副作用,在被调用函数主体中的每个表达式或语句执行之前排序.
[注意:与不同参数表达式相关的值计算和副作用是无序的。 —end note ]
调用函数(包括其他函数调用)中的每个求值,如果在被调用函数的主体执行之前或之后没有明确排序,则相对于调用函数.9 C++ 中的几个上下文会导致对函数调用进行评估,即使在翻译单元中没有出现相应的函数调用语法。 [ ... ]
被调用函数的执行顺序约束(如上所述)是函数调用的评估特征,无论调用函数的表达式的语法是什么。

其他相关引述是关于std::move

模板类型名 remove_reference::type&& move(T&& t) noexcept;
返回:static_cast(t)。

还有std::unique_ptr&lt;T&gt;.operator-&gt;()

20.7.1.2.4 unique_ptr 观察者

指针运算符->() const noexcept;
要求:get() != nullptr.
返回:get()。

memfunc 按值获取参数,因此我们有 3 个调用:
一)obj-&gt;memfunc b) std::move(obj)
c) 传递参数的移动构造函数。
因为 b) 没有改变任何东西,我们可以忽略它的论点:

a 和 c 的顺序是不确定的,所以任何一个都可以在另一个之前。
如果a先发生,一切都很好,c改变obj无所谓。
如果 c 先发生,则 a 被评估为零 obj,违反了先决条件,所以我们有 UB。

总而言之,它是未定义的行为,因为允许的订单之一具有未定义的行为。

Post-C++17,定义明确:

8.2.2 函数调用[expr.call]

1 函数调用是一个后缀表达式,后跟括号,其中包含一个可能为空的、以逗号分隔的 initializer-clauses 列表,这些列表构成函数的参数。 [...]
[...]
5 postfix-expressionexpression-list 中的每个 expression 和任何默认参数之前排序。 [...]
[...]

【讨论】:

  • 我觉得最重要的部分是你没有加粗的部分:引用 “C++ 中的几个上下文会导致函数调用的评估,甚至虽然翻译单元中没有出现相应的函数调用语法。”,即这包括任何转换/复制到实际参数。
  • 还有 expr.call/4 "每个参数的初始化和销毁​​都发生在调用函数的上下文中。"
【解决方案2】:

是的x 的评估可以在f 的评估之前、之后或期间进行(它们是未排序的)。

[ 注意: 后缀表达式和参数表达式的求值都是无序的 相对于彼此。参数表达式评估的所有副作用都在函数之前排序 输入(见 1.9)。 — 尾注 ]

(C++11,§5.2.2/8)

【讨论】:

  • 为什么引用非规范性注释而不是引用的规范性文本?
  • @Deduplicator,我不认为 1.9 明确说明表示要调用的函数的后缀表达式的评估相对于参数评估是无序的。
  • 是否包括构造参数?
  • @ThomasMatthews: f 不是函数调用。
  • @ThomasMatthews:他们说的不是“调用前”而是“评估前”,这意味着计算表达式f 以获得一个入口点,执行应该跳转的地方(拨打电话) .
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-26
  • 2010-10-18
  • 1970-01-01
  • 2021-03-15
相关资源
最近更新 更多