visual studio microsoft español descargar community c++ c++11 g++ clang

c++ - microsoft - visual studio installer



Vida útil de los objetos temporales durante la inicialización de la lista. (1)

Este es el problema central 1343 "Secuenciación de no inicialización de clase" , que fue aceptado como Informe de P0570R0 en noviembre de 2016 por el documento P0570R0 . La resolución propuesta es parte de C ++ 17 pero, por lo tanto, no es parte de C ++ 14, por lo que (a menos que el comité decida publicar un corrigendum para C ++ 14) este es un punto de diferencia entre C ++ 17 y C + +14.

C ++ 14

La salida correcta de acuerdo con las reglas de la norma C ++ 14 es 1, 1 para la matriz y 1, 2 para el vector; esto se debe a que la construcción de un vector (incluso a partir de una lista-iniciada con refuerzos ) requiere una llamada a un constructor, mientras que la construcción de una matriz no lo hace.

El lenguaje que gobierna esto está en [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. [...]

Esto está bien como un resumen de nivel superior, pero deja algunas preguntas sin responder:

  • Precisamente qué construcción de lenguaje cuenta como la construcción que produce una llamada implícita de una función;
  • Lo que realmente cuenta como una llamada implícita de una función; presumiblemente, una llamada a un constructor definido por el usuario es una llamada a una función, pero ¿qué pasa con un constructor predeterminado o definido como predeterminado?

Una matriz es un agregado, por lo que se inicializa desde una lista- iniciada de acuerdo con [dcl.init.aggr] ; esto dice que cada elemento se inicializa directamente desde el elemento correspondiente de la lista, por lo que no hay una llamada de función implícita (al menos no correspondiente a la inicialización general). En un nivel de sintaxis, dentro de un inicializador ( [dcl.init] / 1) que usa una lista de iniciados con corchetes como el inicializador de llaves o iguales , las expresiones completas son las expresiones contenidas entre llaves y separadas por comas. Al final de cada expresión completa, se requiere que los destructores de temporarios se ejecuten, ya que ninguno de los tres contextos mencionados en [class.temporary] es el caso aquí.

El caso para la inicialización de un vector es diferente, ya que está utilizando el constructor initializer_list , por lo que ocurre una llamada implícita de una función (es decir, el constructor initializer_list ); esto significa que hay una expresión completa implícita que rodea la inicialización completa, por lo que los temporales se destruyen solo cuando se completa la inicialización del vector.

Confusamente, [dcl.init.list] dice que su código es "aproximadamente equivalente" a:

const int __a[2] = {int{ID().id}, int{ID().id}}; // #1 std::vector<int> vec(std::initializer_list<int>(__a, __a + 2));

Sin embargo, esto debe leerse en contexto; por ejemplo, la matriz que respalda la lista de initializer_list tiene un tiempo de vida limitado por la inicialización del vector.

Esto fue mucho más claro en C ++ 03, que tenía en [intro.execution] :

13 - [ Nota: ciertos contextos en C ++ provocan la evaluación de una expresión completa que resulta de un constructo sintáctico distinto de la expresión (5.18). Por ejemplo, en 8.5, una sintaxis para el inicializador es ( expression-list ) pero la construcción resultante es una llamada a la función en una función constructora con la lista de expresiones como una lista de argumentos; tal llamada de función es una expresión completa. Por ejemplo, en 8.5, otra sintaxis para el inicializador es = initializer-clause pero nuevamente la construcción resultante podría ser una llamada a la función en una función constructora con una expresión-asignación como un argumento; de nuevo, la llamada a la función es una expresión completa. ]

Este párrafo está tachado en su totalidad de C ++ 11; Esto fue por la resolución a CWG 392 . La confusión resultante probablemente no fue la intención.

C ++ 17

Después de P0570R0, [intro.execution] indica que una expresión completa es: [...]

  • un declarador de inicio ([dcl.decl]) [...] que incluye las expresiones constituyentes del inicializador, o [...]
  • una expresión que no es una subexpresión de otra expresión y que de otra manera no forma parte de una expresión completa.

Así que en C ++ 17, la expresión completa es arr[]{ID().id, ID().id} y vec{ID().id, ID().id} respectivamente, y la salida correcta es 1, 2 en cada caso, ya que la destrucción de la primera ID temporal se aplaza hasta el final de la expresión completa.

Siempre asumí que los objetos temporales viven hasta el final de una expresión completa. Sin embargo, aquí hay una curiosa diferencia entre las inicializaciones de un std::vector y una matriz.

Por favor considere el siguiente código:

#include <iostream> #include <vector> struct ID{ static int cnt; // the number of living object of class ID at the moment of creation: int id; ID():id(++cnt){} ~ID(){ cnt--; } }; int ID::cnt=0; int main(){ int arr[]{ID().id, ID().id}; std::vector<int> vec{ID().id, ID().id}; std::cout<<" Array: "<<arr[0]<<", "<<arr[1]<<"/n"; std::cout<<" Vector: "<<vec[0]<<", "<<vec[1]<<"/n"; }

La salida de este programa es un poco inesperada (al menos para mí):

Array: 1, 1 Vector: 1, 2

Esto significa que los objetos temporales están vivos durante toda la inicialización del std::vector pero se crean y se destruyen uno tras otro en el caso de una matriz. Espero que los temporales vivan hasta la expresión completa int arr[]{ID().id, ID().id}; esta completado.

El estándar menciona una excepción relacionada con la vida útil de los objetos temporales y la inicialización de matrices (12.2). Sin embargo, no entiendo su significado y no sé por qué se aplica en este caso particular:

Hay dos contextos en los que los temporales se destruyen en un punto diferente al final de la expresión completa. El primer contexto es cuando se llama a un constructor predeterminado para inicializar un elemento de una matriz. Si el constructor tiene uno o más argumentos predeterminados, la destrucción de cada temporal creado en un argumento predeterminado se secuencia antes de la construcción del siguiente elemento de la matriz, si existe.

Visión general de los resultados con diferentes compiladores (el resultado de MSVS es una cortesía de NathanOliver):

Array Vector clang 3.8 1, 2 1, 2 g++ 6.1 1, 1 1, 2 icpc 16 1, 1 1, 2 MSVS 2015 1, 1 1, 2

Como ecatmur señaló, para la inicialización agregada, cada elemento de la lista-init reforzada es una expresión completa, por lo tanto, el siguiente código

struct S{ int a; int b; } s{ID().id, ID().id}; std::cout<<" Struct: "<<s.a<<", "<<s.b<<"/n";

Debe imprimir Struct 1, 1 a la consola. Eso es exactamente lo que hace el programa compilado por g ++. Sin embargo, clang parece tener un error: el programa resultante imprime Struct 1, 2 .

Se ha informado de un error en clang: https://llvm.org/bugs/show_bug.cgi?id=29080