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

9

Этот вопрос вытекает из этого комментария: объяснение времени жизни лямбда для сопрограмм 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

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

[...]

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

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

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

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

Майк Луи
источник
2
Возможно, следующий ответ имеет отношение к stackoverflow.com/a/60495359/12345656 Это выглядит довольно по-другому, но это также касается функции-члена, во время выполнения которой thisуказатель становится недействительным. Учтите также обсуждение в комментариях.
n314159

Ответы:

2

[dcl.fct.def.coroutine] p3 :

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

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

Однако, на заметку об объектах, уничтожаемых во время выполнения функции-члена, это действительно хорошо само по себе, и никто, кроме самого стандарта, не подразумевает этого в [basic] :

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

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

(NB: UB выше, потому что неявное thisне стирается и все еще ссылается на неявный параметр объекта.)

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

Columbo
источник
Вы имеете в виду «возобновление и завершение» в конце?
Дэвис Херринг
@DavisHerring Нет, я имел в виду именно в этом «внешнем» временном интервале, где неясно, может ли ссылка быть назначена новой ссылке и т. Д., Что потребует реального объекта. Тот факт, что ссылка не доступна скрытно, важно, чтобы это не было UB
Коломбо
Но этого недостаточно, чтобы оставить висячую ссылку в одиночестве до возобновления; Вы должны оставить его в покое ( например , в лямбда-теле) навсегда - до конца его жизни, то есть до завершения. Так что, возможно, это должно быть «приостановка и завершение».
Дэвис Херринг
@DavisHerring Я специально упомянул этот интервал, потому что в нашем примере мы знаем, что другой является безопасным.
Коломбо
Конечно; Я просто нахожу формулировку запутанной. Может быть, никто не делает.
Дэвис Херринг