Это UB, чтобы возобновить сопрограмму функции-члена объекта, время жизни которого закончилось? - PullRequest
9 голосов
/ 11 марта 2020

Этот вопрос вытекает из этого комментария: Объяснение времени жизни лямбды для сопрограмм C ++ 20

относительно этого примера:

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

... любой указатель, представляющий адрес места хранения, где объект будет или был расположен, может использоваться, но только ограниченным образом. [...] Программа имеет неопределенное поведение, если:

[...]

- указатель используется для доступа к элементу данных, не являющемуся c или вызовите нестати c функцию-члена объекта или

Однако в этом примере:

  • оператор () лямбды вызывается, пока время жизни лямбды все еще действует
  • Затем оно приостанавливается,
  • , затем лямбда уничтожается,
  • и затем функция-член (оператор () ) возобновляется в какой-то момент позже.

Считается ли это возобновление неопределенным поведением?

1 Ответ

2 голосов
/ 11 марта 2020

[dcl.fct.def.coroutine] p3 :

Тип обещания сопрограммы равен std::coroutine_traits<R, P1, ..., Pn>::promise_type, где R является типом возвращаемого значения функции, а P1 ... Pn является последовательностью типов параметров функции, предшествует тип неявного параметра объекта (12.4.1), если сопрограмма не является статической c функция-член.

Неявный параметр объекта в вашем примере является константной ссылкой, и, следовательно, эта ссылка будет зависать при возобновлении выполнения после уничтожения объекта закрытия.

Однако, на заметку об объектах, уничтожаемых во время выполнения функции-члена, это действительно хорошо само по себе, и никто, кроме самого стандарта, не подразумевает этого в [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 не стирается и по-прежнему ссылается на параметр неявного объекта.)

Таким образом, ваш пример выглядит четко определенным, при условии, что возобновление выполнения не подпадает под те же правила, что и исходный вызов. Обратите внимание, что ссылка на объект замыкания может быть висящей, но между приостановкой и возобновлением к ней нет доступа.

...