¿Es UB reanudar una rutina de función miembro de un objeto cuya vida útil ha finalizado?

9

Esta pregunta surge de este comentario: explicación de por vida de Lambda para las corutinas de C ++ 20

con respecto a este ejemplo:

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

Entonces, la pregunta es si ejecutar la corutina devuelta por fooUB.

"Llamar" a una función miembro (una vez finalizada la vida útil del objeto) es UB: http://eel.is/c++draft/basic.life#6.2

... se puede usar cualquier puntero que represente la dirección de la ubicación de almacenamiento donde se ubicará o se ubicará el objeto, pero solo de manera limitada. [...] El programa tiene un comportamiento indefinido si:

[...]

- el puntero se usa para acceder a un miembro de datos no estático o llamar a una función de miembro no estático del objeto , o

Sin embargo, en este ejemplo:

  • ()se llama al operador de la lambda mientras la vida útil de la lambda sigue siendo válida
  • Luego se suspende,
  • entonces la lambda es destruida
  • y luego la función miembro (operador ()) se reanuda en algún momento posterior.

¿Esta reanudación se considera un comportamiento indefinido?

Mike Lui
fuente
2
Quizás la siguiente respuesta sea relevante stackoverflow.com/a/60495359/12345656 Parece bastante diferente, pero también se trata de una función miembro durante la cual thisse invalida la ejecución del puntero. Considere también la discusión en los comentarios.
n314159

Respuestas:

2

[dcl.fct.def.coroutine] p3 :

El tipo de promesa de una rutina es std::coroutine_traits<R, P1, ..., Pn>::promise_type, donde Res el tipo de retorno de la función, y P1 ... Pnson la secuencia de tipos de los parámetros de la función, precedidos por el tipo del parámetro de objeto implícito (12.4.1) si la rutina es no estática función miembro

El parámetro objeto implícito es en su ejemplo una referencia constante, y por lo tanto esa referencia estará colgando cuando se reanude la ejecución después de que el objeto de cierre haya sido destruido.

Sin embargo, en la nota de los objetos que se destruyen durante la ejecución de una función miembro, esto de hecho está bien per se, y nada más que el estándar en sí implica esto en [básico] :

Antes de que se inicie la vida útil de un objeto, pero después de que se haya asignado el almacenamiento que ocupará el objeto o, después de que haya finalizado la vida útil de un objeto y antes de que se reutilice o se libere el almacenamiento que ocupó el objeto, cualquier puntero que represente la dirección de la ubicación de almacenamiento donde se ubicará o se ubicará el objeto se puede usar, pero solo de manera limitada. [...]

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

(NB: el UB anterior se debe a que lo implícito thisno se lava y aún se refiere al parámetro del objeto implícito).

Por lo tanto, su ejemplo parece estar bien definido, condicional a la idea de que la reanudación de la ejecución no se ajusta a las mismas reglas que una invocación original. Tenga en cuenta que la referencia al objeto de cierre puede estar colgando, pero no se accede de ninguna manera entre la suspensión y la reanudación.

Columbo
fuente
¿Quiere decir "reanudación y finalización" al final?
Davis Herring
@DavisHerring No, quise decir específicamente dentro de ese marco de tiempo "exterior", donde no está claro si la referencia podría asignarse a una nueva referencia, etc., que requeriría un objeto real. El hecho de que no se acceda a la referencia de forma oculta es importante para que esto no sea UB
Columbo
Pero no es suficiente dejar sola la referencia colgante hasta que se reanude; tienes que dejarlo solo ( por ejemplo , en el cuerpo lambda) para siempre, por el resto de su vida, que es hasta que se complete. Entonces tal vez debería ser "suspensión y finalización".
Davis Herring
@DavisHerring Mencioné específicamente ese intervalo, porque en nuestro ejemplo sabemos que el otro está a salvo.
Columbo
Por supuesto; Solo encuentro la redacción confusa. Quizás nadie más lo haga.
Davis Herring