c++ c++14 language-lawyer function-calls sequencing

c++ - Secuenciación de la función de destrucción de parámetros.



c++14 language-lawyer (1)

En realidad, puedo responder a mi propia pregunta ... no encontré una respuesta durante la búsqueda antes de escribirla, pero luego, al buscar nuevamente, sí encontré una respuesta (típico, eh).

De todos modos: este problema es CWG # 1880 con la resolución:

Notas de la reunión de junio de 2014:

WG decidió no especificar si los objetos de parámetros se destruyen inmediatamente después de la llamada o al final de la expresión completa a la que pertenece la llamada.

El último borrador de C ++ 17 que tengo (N4606) ha cambiado el texto en [expr.call] / 4:

Se define por la implementación si la vida útil de un parámetro finaliza cuando la función en la que se define devuelve o al final de la expresión completa que lo contiene.

Supongo que deberíamos tratar esta resolución (es decir, "definida por la implementación") como una aplicación retroactiva, ya que no está bien especificada por los estándares publicados.

Nota: La definición de expresión completa se puede encontrar en C ++ 14 [intro.execution] / 10:

Una expresión completa es una expresión que no es una subexpresión de otra expresión. [...] Si una construcción de lenguaje se define para producir una llamada implícita de una función, un uso de la construcción de lenguaje se considera una expresión para los propósitos de esta definición.

Entonces F v { func(0) }; es la expresión completa que encierra para gparm (¡aunque sea una declaración y no una expresión!).

Según C ++ 14 [expr.call] / 4:

La vida útil de un parámetro finaliza cuando la función en la que se define devuelve.

Esto parece implicar que el destructor de un parámetro debe ejecutarse antes de que el código que llamó a la función continúe utilizando el valor de retorno de la función.

Sin embargo, este código muestra de manera diferente:

#include <iostream> struct G { G(int): moved(0) { std::cout << "G(int)/n"; } G(G&&): moved(1) { std::cout << "G(G&&)/n"; } ~G() { std::cout << (moved ? "~G(G&&)/n" : "~G()/n"); } int moved; }; struct F { F(int) { std::cout << "F(int)/n"; } ~F() { std::cout << "~F()/n"; } }; int func(G gparm) { std::cout << "---- In func./n"; return 0; } int main() { F v { func(0) }; std::cout << "---- End of main./n"; return 0; }

La salida para gcc y clang, con -fno-elide-constructors , es (con mis anotaciones):

G(int) // Temporary used to copy-initialize gparm G(G&&) // gparm ---- In func. F(int) // v ~G(G&&) // gparm ~G() // Temporary used to copy-initialize gparm ---- End of main. ~F() // v

Entonces, el constructor de v claramente se ejecuta antes del gparm de gparm . Pero en gparm , gparm se destruye antes de que se ejecute el constructor de v .

El mismo problema se puede ver con copy-elision habilitado y / o con func({0}) para que el parámetro se inicialice directamente. v siempre se construye antes de que se destruya gparm . También observé el problema en una cadena más larga, por ejemplo, F v = f(g(h(i(j()))); no destruyó ninguno de los parámetros de f,g,h,i hasta después de que se inicializó v .

Esto podría ser un problema en la práctica, por ejemplo, si ~G desbloquea un recurso y F() adquiere, sería un punto muerto. O, si ~G lanza, entonces la ejecución debería saltar a un controlador de captura sin que se haya inicializado v .

Mi pregunta es: ¿el estándar permite estos dos pedidos? . ¿Hay alguna definición más específica de la relación de secuencia que involucre la destrucción de parámetros, que solo la cita de expr.call/4 que no usa los términos de secuencia estándar?