¿Es un comportamiento definido hacer referencia a un miembro temprano de una expresión de miembro posterior durante la inicialización agregada?


Considere lo siguiente:

struct mystruct
{
    int i;
    int j;
};

int main(int argc, char* argv[])
{
    mystruct foo{45, foo.i};   

    std::cout << foo.i << ", " << foo.j << std::endl;

    return 0;
}

Tenga en cuenta el uso de foo.i en la lista aggregate-initializer.

g++ 5.2.0 salidas

45, 45

¿Este comportamiento está bien definido? ¿foo.i en este inicializador agregado siempre se garantiza que se refiera al elemento i de la estructura que se está creando (y &foo.i se referiría a esa dirección de memoria, por ejemplo)?

Si agrego un constructor explícito a mystruct:

mystruct(int i, int j) : i(i), j(j) { }

Entonces obtengo lo siguiente advertencias:

main.cpp:15:20: warning: 'foo.a::i' is used uninitialized in this function [-Wuninitialized]
     a foo{45, foo.i};
                ^
main.cpp:19:34: warning: 'foo.a::i' is used uninitialized in this function [-Wuninitialized]
     cout << foo.i << ", " << foo.j << endl;

El código se compila y la salida es:

45, 0

Claramente esto hace algo diferente, y estoy asumiendo que esto es un comportamiento indefinido. Es? Si es así, ¿por qué la diferencia entre esto y cuando no había constructor? Y, ¿cómo puedo obtener el comportamiento inicial (si era un comportamiento bien definido) con un constructor definido por el usuario?

Author: Claudiu, 2015-10-05

4 answers

Su segundo caso es un comportamiento indefinido, ya no está utilizando la inicialización agregada, sigue siendo la inicialización de lista, pero en este caso tiene un constructor definido por el usuario al que se está llamando. Para pasar el segundo argumento a su constructor necesita evaluar foo.i pero aún no se inicializa ya que aún no ha ingresado al constructor y por lo tanto está produciendo un valor indeterminado y producir un valor indeterminado es indefinido comportamiento.

También tenemos la sección 12.7 Construcción y destrucción [clase.cdtor] que dice:

Para un objeto con un constructor no trivial, refiriéndose a cualquier miembro no estático o clase base del objeto antes de que el constructor comience la ejecución resulta en un comportamiento indefinido [...]

Así que no veo una manera de hacer que tu segundo ejemplo funcione como tu primer ejemplo, asumiendo que el primer ejemplo es realmente válido.

Su primer caso parece al igual que debe estar bien definido, pero no puedo encontrar una referencia en el proyecto de norma que parece hacer eso explícito. Tal vez sea un defecto, pero de lo contrario sería un comportamiento indefinido, ya que el estándar no define el comportamiento. Lo que el estándar nos dice es que los inicializadores se evalúan en orden y los efectos secundarios se secuencian, desde la sección 8.5.4 [dcl.init.lista]:

Dentro de la lista inicializador de una lista de inicio entre corchetes, las cláusulas inicializador, incluyendo cualquier resultado del paquete las expansiones (14.5.3), se evalúan en el orden en que aparecen. Es decir, cada cálculo de valor y efecto secundario asociado con un inicializador dado: la cláusula se secuenciaantes antes de cada cálculo de valor y lado efecto asociado a cualquier cláusula initializer-clause que le siga en la lista separada por comas de la lista initializer-list. [...]

Pero no tenemos un texto explícito que diga que los miembros se inicializan después de que cada elemento evaluar.

MSalters argumenta que la sección 1.9 que dice:

Acceder a un objeto designado por un glvalue volátil (3.10), modificar un objeto , llamar a una E/S de biblioteca función, o llamar a una función que hace cualquiera de esas operaciones son todos los efectos secundarios, que son cambios en el estado del entorno de ejecución. [...]

Combinado con:

[...] muy valor de cálculo y efecto secundario asociado con un determinado la cláusula initializer-clause se secuenciará antes de cada cálculo de valor y efecto secundario asociado con cualquier cláusula initializer-clause que le siga [...]

Es suficiente para garantizar que cada miembro del agregado se inicialice a medida que se evalúan los elementos de la lista del inicializador. Aunque esto no se aplicaría antes de C++11 ya que el orden de evaluación de la lista inicializadora no estaba especificado.

Como referencia si el estándar no impone un requisito el comportamiento es indefinido de la sección 1.3.24 que define el comportamiento indefinido:

Comportamiento para el cual esta Norma Internacional no impone requisitos [Nota: Se puede esperar un comportamiento indefinido cuando este Estándar Internacional omite cualquier definición explícita de comportamiento o [...]

Update

Johannes Schaub señala informe de defectos 1343: La secuencia de inicialización no-clase y los hilos de discusión std Es miembro agregado ¿la inicialización de copia asociada con la cláusula initializer correspondiente? y ¿La inicialización de copia de un miembro agregado está asociada con la cláusula inicializador correspondiente? que son todas relevantes.

Básicamente señalan que el primer caso no está actualmente especificado, voy a citar a Richard Smith :

Así que la única pregunta es, es el efecto secundario de inicializar s. i "asociado con" la evaluación de la expresión "5"? Creo que la única suposición razonable es que es: si 5 inicializara un miembro del tipo de clase, la llamada al constructor obviamente sería parte de la expresión completa por la definición en [intro.ejecución] p10, por lo que es natural asumir que lo mismo es cierto para los tipos escalares.

Sin embargo, no creo que el estándar en realidad diga explícitamente esto dondequiera.

Así que aunque como se indica en varios lugares parece que las implementaciones actuales hacen lo que esperamos, parece que no es prudente confiar en él hasta que esto se aclare oficialmente o las implementaciones proporcionen una garantía.

Actualización de C++20

Con la propuesta de inicialización designada : P0329 la respuesta a esta pregunta cambia para el primer caso. Contiene la siguiente sección:

Añádase un nuevo párrafo a 11.6.1 [dcl.init.aggr]:

Las inicializaciones de los elementos del agregado se evalúan en el orden de los elementos. Eso es, todos los cálculos de valor y los efectos secundarios asociados con un elemento dado se secuencian antes

Podemos ver que esto se refleja en el último borrador de norma

 14
Author: Shafik Yaghmour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-11 13:07:34

De [dcl.init.aggr] 8.5.1(2)

Cuando un agregado es inicializado por una lista inicializadora, como se especifica en 8.5.4, los elementos de la lista inicializadora se toman como inicializadores para los miembros del agregado, en subíndice creciente o en orden miembro. Cada miembro es copiado-inicializado desde la cláusula initializer-correspondiente.

énfasis mío

Y

Dentro de la lista de inicialización de una lista de inicio entre corchetes, el las cláusulas de inicialización, incluidas las que resulten de expansiones de paquetes (14.5.3), se evalúan en el orden en que aparecen. Es decir, cada cálculo de valor y efecto secundario asociado con una cláusula initializer-clause dada se secuencian antes de cada cálculo de valor y efecto secundario asociado con cualquier cláusula initializer-clause que le siga en la lista separada por comas de la lista initializer-list.

Me lleva a creer que cada miembro de la clase será inicializado en el orden en que son declarado en la lista de inicialización y dado que foo.i se inicializa antes de evaluarlo para inicializar j este debe ser el comportamiento definido.

Esto también está respaldado con [intro.ejecución] 1.9 (12)

Acceder a un objeto designado por un glvalue volátil (3.10), modificar un objeto, llamar a una función de E/S de biblioteca o llamar a una función que realiza cualquiera de esas operaciones son todos efectos secundarios, que son cambios en el estado de la ejecución ambiente.

énfasis mío

En su segundo ejemplo no estamos usando inicialización agregada sino inicialización de lista. [dcl.init.list] 8.5.4 (3) has

Lista-inicialización de un objeto o referencia de tipo T se define de la siguiente manera:
[...]
- De lo contrario, si T es un tipo de clase, se consideran constructores. Se enumeran los constructores aplicables y el mejor se elige a través de la resolución de sobrecarga (13.3, 13.3.1.7).

Así que ahora llamaríamos a su constructor. Al llamar al constructor foo.i no se ha inicializado, por lo que estamos copiando una variable no inicializada que es un comportamiento indefinido.

 12
Author: NathanOliver,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-06 13:50:59

Mi primera idea fue UB, pero estás completamente en el caso de la inicialización agregada. El borrador n4296 para la especificación C++ 11 es explícito en los agregados 8.5.1 [dcl.init.párrafo aggr]:

Un aggregate es una matriz o una clase sin constructores proporcionados por el usuario , sin miembros de datos no estáticos privados o protegidos, sin clases base y sin funciones virtuales

Más tarde:

Cuando un agregado es inicializado por una lista inicializadora, como se especifica en 8.5.4, elementos de la lista inicializador se toman como inicializadores para los miembros del agregado, en subíndice creciente u orden de miembros

(enfatizar el mío)

Mi entendimiento es que mystruct foo{45, foo.i}; primero inicializa foo.i con 45, luego foo.j con foo.i.

No me atrevería a usar eso en código real de todos modos, porque incluso si creo que está definido por estándar, temería que un programador de compiladores haya pensado de manera diferente...

 1
Author: Serge Ballesta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-05 15:08:13

¿Cómo puedo obtener el comportamiento inicial (si era un comportamiento bien definido) con un constructor definido por el usuario?

Pasar el parámetro por referencia para ese parámetro que se refiere al parámetro inicializado previamente del objeto que se construye, de la siguiente manera:

 mystruct(int i, int& j):i(i),j(j)
 -2
Author: cm161,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-05 18:42:21