dev descargar compiler c++ c++11

descargar - C++ 11 comportamiento de inicialización de llaves extraño



c++14 (2)

No entiendo cómo funcionan aquí las reglas de inicialización de refuerzo de C ++ 11. Teniendo este código:

struct Position_pod { int x,y,z; }; class Position { public: Position(int x=0, int y=0, int z=0):x(x),y(y),z(z){} int x,y,z; }; struct text_descriptor { int id; Position_pod pos; const int &constNum; }; struct text_descriptor td[3] = { {0, {465,223}, 123}, {1, {465,262}, 123}, }; int main() { return 0; }

Tenga en cuenta que se declara que la matriz tiene 3 elementos, pero solo se proporcionan 2 inicializadores.

Sin embargo, se compila sin errores, lo que suena extraño, ya que el miembro de referencia del último elemento de la matriz quedará sin inicializar. De hecho, tiene valor NULL:

(gdb) p td[2].constNum $2 = (const int &) @0x0: <error reading variable>

Y ahora la "magia": cambié Position_pod a Position

struct text_descriptor { int id; Position_pod pos; const int &constNum; };

se convierte en esto:

struct text_descriptor { int id; Position pos; const int &constNum; };

Y ahora da el error esperado:

error: uninitialized const member ‘text_descriptor::constNum''

Mi pregunta: por qué se compila en el primer caso, cuando debería dar un error (como en el segundo caso). La diferencia es que Position_pod usa la inicialización de llaves de estilo C y Position usa la inicialización de estilo C ++ 11, que llama al constructor de Position. Pero, ¿cómo afecta esto la posibilidad de dejar un miembro de referencia sin inicializar?

(Actualización) Compilador: gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2


La primera versión (la que tiene el sufijo _pod) todavía funciona pero no da error porque para el valor z, se elige el valor predeterminado de int (el 0). Idem para la referencia int int.

En la segunda versión, no puede definir una referencia constante sin darle un valor. El compilador le da un error porque más adelante no puede asignarle ningún valor.

Además, el compilador que estás usando juega un papel importante aquí, tal vez sea un error, simplemente porque estás declarando un miembro de la clase antes que un miembro int.


Quedará claro que

struct text_descriptor td[3] = { {0, {465,223}, 123}, {1, {465,262}, 123}, };

es list-initialization, y que la lista de inicializadores no está vacía.

C ++ 11 dice ([dcl.init.list] p3):

La inicialización de lista de un objeto o referencia de tipo T se define de la siguiente manera:

  • Si la lista de inicializadores no tiene elementos y T es un tipo de clase con un constructor predeterminado, el objeto se inicializa con valores.
  • De lo contrario, si T es un agregado, se realiza la inicialización agregada (8.5.1).
  • ...

[dcl.init.aggr] p1:

Un agregado es una matriz o una clase (Cláusula 9) sin constructores proporcionados por el usuario (12.1), sin inicializadores con refuerzo o igual para miembros de datos no estáticos (9.2), no hay miembros de datos no estáticos privados o protegidos ( Cláusula 11), sin clases base (Cláusula 10) y sin funciones virtuales (10.3).

td es una matriz, por lo que es un agregado, por lo que se realiza la inicialización agregada.

[dcl.init.aggr] p7:

Si hay menos cláusulas de inicialización en la lista que miembros en el agregado, entonces cada miembro que no se inicialice explícitamente se inicializará desde una lista de inicialización vacía (8.5.4).

Este es el caso aquí, por lo que td[2] se inicializa desde una lista de inicializadores vacía, que ([dcl.init.list] p3 de nuevo) significa que se ha inicializado con valores.

La inicialización del valor, a su vez, significa ([dcl.init] p7):

Para inicializar con valor un objeto de tipo T significa:

  • si T es un tipo de clase (posiblemente cv-calificado) (Cláusula 9) con un constructor provisto por el usuario (12.1), ...
  • si T es un tipo de clase sin unión (posiblemente cv calificado) sin un constructor provisto por el usuario, entonces el objeto tiene inicialización cero y, si el constructor predeterminado declarado implícitamente por T no es trivial, se llama a ese constructor.
  • ...

Su clase text_descriptor es una clase sin constructor proporcionado por el usuario, por lo que td[2] se inicializa primero con cero, y luego se llama a su constructor.

Medios de inicialización cero ([dcl.init] p5):

Para inicializar en cero un objeto o referencia de tipo T significa:

  • si T es un tipo escalar (3.9), ...
  • si T es un tipo de clase no sindicalizada (posiblemente cv calificado), cada miembro de datos no estáticos y cada subobjeto de clase base se inicializan con cero y el relleno se inicializa a cero bits;
  • si T es un tipo de unión (posiblemente cv-calificado), ...
  • si T es un tipo de matriz, ...
  • si T es un tipo de referencia, no se realiza ninguna inicialización.

Esto está bien definido, independientemente del constructor predeterminado de text_descriptor : simplemente inicializa en cero a los miembros y sub-miembros que no son de referencia.

Luego se llama al constructor predeterminado, si no es trivial. Así es como se define el constructor predeterminado ([especial] p5):

Un constructor predeterminado para una clase X es un constructor de clase X que se puede llamar sin un argumento. Si no hay un constructor declarado por el usuario para la clase X , un constructor que no tiene parámetros se declara implícitamente como predeterminado (8.4). Un constructor predeterminado declarado implícitamente es un miembro público en línea de su clase. Un constructor predeterminado predeterminado para la clase X se define como eliminado si:

  • ...
  • cualquier miembro de datos no estáticos sin un inicializador de refuerzo o igual es de tipo de referencia,
  • ...

Un constructor predeterminado es trivial si no es proporcionado por el usuario y si:

  • su clase no tiene funciones virtuales (10.3) ni clases de base virtual (10.1), y
  • ningún miembro de datos no estáticos de su clase tiene un inicializador de corsé o igual, y
  • todas las clases base directas de su clase tienen constructores por defecto triviales, y
  • para todos los miembros de datos no estáticos de su clase que son de tipo de clase (o matriz de ellos), cada clase tiene un constructor predeterminado trivial.

De lo contrario, el constructor predeterminado no es trivial.

Por lo tanto, el constructor definido implícitamente se elimina, como se esperaba, pero también es trivial, si pos es un tipo POD (!). Debido a que el constructor es trivial, no se llama. Debido a que no se llama al constructor, el hecho de que se elimine no es un problema.

Este es un agujero abierto en C ++ 11, que desde entonces ha sido reparado. Resulta que se ha corregido para tratar con constructores predeterminados triviales inaccesibles , pero la redacción fija también cubre constructores predeterminados triviales eliminados N4140 (aproximadamente C ++ 14) dice en [dcl.init.aggr] p7 (énfasis mío):

  • si T es un tipo de clase (posiblemente cv calificado) sin un constructor predeterminado provisto por el usuario o eliminado, entonces el objeto tiene inicialización cero y se comprueban las restricciones semánticas para la inicialización predeterminada , y si T tiene un constructor predeterminado no trivial , el objeto está inicializado por defecto;

Como TC señaló en los comentarios, otro DR también cambió para que td[2] aún se inicialice desde una lista de inicializadores vacía, pero esa lista de inicializadores vacía ahora implica una inicialización agregada. Eso, a su vez, implica que cada uno de los miembros de td[2] se inicializa también desde una lista de inicializadores vacía ([dcl.init.aggr] p7), por lo que parece que se inicialice el miembro de referencia desde {} .

[dcl.init.aggr] p9 luego dice (como remyabel había señalado en una respuesta ahora eliminada):

Si un inicializador-lista incompleto o vacío deja un miembro del tipo de referencia sin inicializar, el programa no está bien formado.

No me queda claro si esto se aplica a las referencias inicializadas desde el implícito {} , pero los compiladores sí lo interpretan como tal, y no hay mucho más que pueda significar.