8

这个问题源于此评论:C++ 20 coroutines 的 Lambda 生命周期解释

关于这个例子:

auto foo() -> folly::coro::Task<int> {
    auto task = []() -> folly::coro::Task<int> {
        co_return 1;
    }();
    return task;
}

所以问题是执行返回的协程是否foo会导致UB。

“调用”成员函数(在对象的生命周期结束后)是 UB:http ://eel.is/c++draft/basic.life#6.2

...可以使用任何表示对象将要或曾经位于的存储位置的地址的指针,但只能以有限的方式使用。[...]如果出现以下情况,该程序具有未定义的行为:

[...]

-- 指针用于访问非静态数据成员或调用对象的非静态成员函数,或

但是,在此示例中:

  • ()在 lambda 的生命周期仍然有效时调用 lambda的运算符
  • 然后暂停,
  • 然后 lambda 被销毁,
  • 然后成员函数(运算符())在之后的某个时间点恢复。

这种恢复是否被认为是未定义的行为?

4

1 回答 1

2

[dcl.fct.def.coroutine]p3

协程的承诺类型std::coroutine_traits<R, P1, ..., Pn>::promise_type,其中R是函数的返回类型,并且 P1 ... Pn是函数参数的类型序列, 如果协程是非静态的,则前面是隐式对象参数(12.4.1)的类型成员函数。

在您的示例中,隐式对象参数是一个 const 引用,因此在关闭对象被销毁后恢复执行时,该引用将悬空。

但是,在成员函数执行期间对象被销毁的注意事项上,这本身确实很好,除了标准本身在[basic]中暗示了这一点:

在对象的生命周期开始之前但在对象将占用的存储空间分配之后,或者在对象的生命周期结束之后并且在对象占用的存储空间被重用或释放之前,表示对象地址的任何指针可以使用对象将要或曾经位于的存储位置,但只能以有限的方式使用。[...]

void B::mutate() {
  new (this) D2;    // reuses storage --- ends the lifetime of *this
  f();              // undefined behavior
  ... = this;       // OK, this points to valid memory
}

(注意:上面的 UB 是因为隐式this没有被清洗,仍然是指隐式对象参数。)

因此,您的示例似乎定义明确,以恢复执行不属于与原始调用相同的规则为条件。请注意,对闭包对象的引用可能是悬空的,但在暂停和恢复之间不会以任何方式访问它。

于 2020-03-11T14:28:24.990 回答