Inicialización de agregado
Inicializa un agregado a partir de una lista de inicializadores entre llaves. Es una forma de inicialización de lista (desde C++11).
Contenido |
[editar] Sintaxis
T object = { arg1, arg2, ... };
|
(1) | ||||||||
T object { arg1, arg2, ... };
|
(2) | (desde C++11) | |||||||
T object = { . des1 = arg1 , . des2 { arg2 } ... };
|
(3) | (desde C++20) | |||||||
T object { . des1 = arg1 , . des2 { arg2 } ... };
|
(4) | (desde C++20) | |||||||
[editar] Definiciones
[editar] Agregado
Un agregado es uno de los siguientes tipos:
- tipos array;
- tipos clase que no tienen:
|
(hasta C++11) |
|
(desde C++11) (hasta C++20) |
|
(desde C++20) |
- datos miembros directos no-estáticos privados o protegidos
(hasta C++17) | |
|
(desde C++17) |
- funciones miembro virtuales
(desde C++11) (hasta C++14) |
[editar] Elemento
Los elementos de un agregado son:
- para un array, los elementos del array en orden creciente de subíndice, o
|
(hasta C++17) |
|
(desde C++17) |
[editar] Pertenencia
Se dice que cada cláusula inicializadora en una lista de inicializadores entre llaves pertenece a un elemento del agregado que se está inicializando o a un elemento de uno de sus subagregados.
Teniendo en cuenta la secuencia de cláusulas inicializadoras y la secuencia de elementos agregados inicialmente formada como la secuencia de elementos del agregado que se está inicializando y potencialmente modificada como se describe a continuación:
- Para cada cláusula inicializadora, si se cumple alguna de las siguientes condiciones, pertenece al elemento agregado correspondiente elem:
- elem no es un agregado.
- La cláusula inicializadora comienza con {.
- La cláusula inicializadora es una expresión y se puede formar una secuencia de conversión implícita que convierta la expresión al tipo de elem.
- elem es un agregado que en sí mismo no tiene elementos agregados.
- De lo contrario, elem es un agregado y ese subagregado se reemplaza en la lista de elementos agregados por la secuencia de sus propios elementos agregados, y el análisis de pertenencia se reanuda con el primer elemento de ese tipo y la misma cláusula inicializadora. En otras palabras, estas reglas se aplican recursivamente a los subagregados del agregado.
El análisis se completa cuando se han agotado todas las cláusulas inicializadoras. Si queda alguna cláusula inicializadora que no pertenezca a un elemento del agregado o a uno de sus subagregados, el programa está mal formado.
struct S1 { int a, b; }; struct S2 { S1 s, t; }; // Cada subagregado de “x” pertenece a una cláusula inicializadora que comienza con {S2 x[2] = { // pertenece a “x[0]” { {1, 2}, // pertenece a “x[0].s” {3, 4} // pertenece a “x[0].t” }, // pertenece a “x[1]” { {5, 6}, // pertenece a “x[1].s” {7, 8} // pertenece a “x[1].t” } }; // “x” e “y” tienen el mismo valor (ver abajo) S2 y[2] = {1, 2, 3, 4, 5, 6, 7, 8}; // El proceso del análisis de pertenencia de “y”: // 1. Inicializa la secuencia de elementos agregados (x[0], x[1]) y // la secuencia de cláusulas inicializadoras (1, 2, 3, 4, 5, 6, 7, 8). // 2. A partir de los primeros elementos de cada secuencia, // verifica si 1 pertenece a x[0]: // · x[0] es un agregado. // · 1 no comienza con {. // · 1 es una expresión, pero no se puede convertir implícitamente a S2. // · x[0] tiene elementos agregados. // 3. 0 no puede pertenecer a x[0], por lo tanto x[0] se reemplaza por x[0].s y x[0].t, // la secuencia de elementos agregados se convierte en (x[0].s, x[0].t, x[1]). // 4. Reanuda la comprobación de pertenencia, pero 1 tampoco puede pertenecer a x[0].s. // 5. La secuencia de elementos agregados ahora se convierte en (x[0].s.a, x[0].s.b, x[0].t, x[1]). // 6. Reanuda nuevamente la comprobación de pertenencia: // 1 pertenece a x[0].s.a, y 2 pertenece a x[0].s.b. // 7. El resto del análisis de pertenencia funciona de manera similar. char cv[4] = {'a', 's', 'd', 'f', 0}; // ERROR: demasiadas cláusulas de inicialización
[editar] Proceso de inicialización
[editar] Determinación del tipo de elemento
Los efectos de la inicialización de agregado son:
|
(desde C++20) |
- De lo contrario, si la lista de inicializadores no está vacía, los elementos inicializados explícitamente del agregado son los elementos con una cláusula de inicializador correspondiente y los elementos que tienen un subagregado con una cláusula de inicializador correspondiente.
- De lo contrario, la lista de inicializadores debe estar vacía ({}), y no hay elementos inicializados explícitamente.
- El programa está mal formado si el agregado es una unión y hay dos o más elementos inicializados explícitamente:
Los efectos de la inicialización de agregado son:
union u { int a; const char* b; }; u a = {1}; // OK: inicializa explícitamente el miembro `a` u b = {0, "asdf"}; // ERROR: inicializa explícitamente dos miembros u c = {"asdf"}; // ERROR: int no puede ser inicializado por "asdf" // Listas de inicializadores designados de C++20 u d = {.b = "asdf"}; // OK: puede inicializar explícitamente un miembro no inicial u e = {.a = 1, .b = "asdf"}; // ERROR: inicializa explícitamente dos miembros
[editar] Elementos inicializados explícitamente
Para cada elemento inicializado explícitamente:
struct C { union { int a; const char* p; }; int x; } c = {.a = 1, .x = 3}; // inicializa c.a con 1 y c.x con 3
|
(desde C++20) |
|
(hasta C++20) |
|
(desde C++20) |
- Si una cláusula inicializadora pertenece al elemento agregado, entonces el elemento agregado se copia por inicialización a partir de la cláusula inicializadora.
- De lo contrario, el elemento agregado se copia por inicialización a partir de una lista de inicializadores entre llaves que consta de todas las cláusulas inicializadoras que pertenecen a subobjetos del elemento agregado, en el orden de aparición.
struct A { int x; struct B { int i; int j; } b; } a = {1, {2, 3}}; // inicializa a.x con 1, a.b.i con 2, a.b.j con 3 struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1{{1, 2}, {}, 4}; // inicializa d1.b1 con 1, d1.b2 con 2, // d1.b3 con 42, d1.d con 4 derived d2{{}, {}, 4}; // inicializa d2.b1 con 0, d2.b2 con 42, // d2.b3 con 42, d2.d con 4
[editar] Elementos inicializados implícitamente
Para un agregado que no es de tipo unión, cada elemento que no es un elemento inicializado explícitamente se inicializa de la siguiente manera:
|
(desde C++11) |
- De lo contrario, si el elemento no es una referencia, el elemento se inicializa por copia a partir de una lista de inicializadores vacía.
- De lo contrario, el programa está mal formado.
struct S { int a; const char* b; int c; int d = b[a]; }; // inicializa ss.a con 1, // ss.b con "asdf", // ss.c con el valor de una expresión de la forma int{} (es decir, 0), // y ss.d con el valor de ss.b[ss.a] (es decir, 's') S ss = {1, "asdf"};
Si el agregado es un tipo unión y la lista de inicializadores está vacía, entonces
|
(desde C++11) |
- De lo contrario, el primer miembro de la unión (si lo hay) se inicializa por copia de una lista de inicializadores vacía.
[editar] Arrays con límites desconocidos
La cantidad de elementos de un array con límites desconocidos inicializado con una lista de inicializadores entre llaves es la cantidad de elementos inicializados explícitamente del array. Un array con límites desconocidos no se puede inicializar con {}.
int x[] = {1, 3, 5}; // x tiene 3 elementos struct Y { int i, j, k; }; Y y[] = {1, 2, 3, 4, 5, 6}; // y has only 2 elementos: // 1, 2 y 3 pertenecen a y[0], // 4, 5 y 6 pertenecen a y[1] int z[] = {} // ERROR: no se puede declarar un array sin ningún elemento
Inicializadores designadosLas formas de sintaxis (3,4) se conocen como inicializadores designados: cada designador debe nombrar un dato miembro directo no estático de T, y todos los designador es utilizados en la expresión deben aparecer en el mismo orden que los datos miembro de T. struct A { int x; int y; int z; }; A a{.y = 2, .x = 1}; // ERROR: el orden del designador no coincide con el orden de declaración A b{.x = 1, .z = 2}; // de acuerdo, b.y inicializada a 0 Cada dato miembro directo no estático nombrado por el inicializador designado se inicializa desde el inicializador correspondiente con llaves o signo igual que sigue al designador. Se prohíben las conversiones de estrechamiento. Un inicializador designado puede utilizarse para inicializar una unión en un estado distinto del primero. Solamente puede proporcionarse un inicializador para una unión. union u { int a; const char* b; }; u f = { .b = "asdf" }; // de acuerdo, el miembro activo de la unión es b u g = { .a = 1, .b = "asdf" }; // ERROR, solamente se puede proporcionar un inicializador Para un agregado que no es de tipo unión, los elementos para los cuales no se proporciona un inicializador designado se inicializan de la misma manera como se describe anteriormente para el caso donde el número de cláusulas de inicializadores es menor que el número de miembros (los inicializadores de miembros por defecto, cuando se proporcionan, de otra manera, inicialización de lista): struct A { string str; int n = 42; int m = -1; }; A{.m=21} // Inicializa str con {}, que llama al constructor por defecto // entonces inicializa a n con = 42 // entonces inicializa a m con = 21 Si el agregado que se inicializa con una cláusula de inicializador designado tiene un miembro de tipo unión anónima, el inicializador designado correspondiente deberá nombrar uno de los miembros de esa unión anónima. Nota: la inicialización designada fuera de orden, la inicialización designada anidada, la mezcla de inicializadores designados con inicializadores regulares, y la inicialización designada de arrays, se admiten todas en el lenguaje de programación C, pero no se permiten en C++. struct A { int x, y; }; struct B { struct A a; }; struct A a = {.y = 1, .x = 2}; // C válido, C++ inválido (fuera de orden) int arr[3] = {[1] = 5}; // C válido, C++ inválido (array) struct B b = {.a.x = 0}; // C válido, C++ inválido (anidado) struct A a = {.x = 1, 2}; // C válido, C++ inválido (mezclado) |
(desde C++20) |
[editar] Arrays de caracteres
Los arrays de tipos carácter ordinarios (char, signed char, unsigned char), char8_t (desde C++20), char16_t, char32_t (desde C++11) o wchar_t se pueden inicializar a partir de literales de cadena ordinarios, literales de cadena UTF-8 (desde C++20), literales de cadena UTF-16, literales de cadena UTF-32 (desde C++11) o literales de cadena ancha, respectivamente, opcionalmente encerrados entre llaves. Además, un array de char o unsigned char puede inicializarse mediante un literal de cadena UTF-8, opcionalmente entre llaves (desde C++20). Los caracteres sucesivos del literal de cadena (que incluye el carácter de terminación nulo implícito) inicializan los elementos del array, con una conversión de entero si es necesario para el valor de origen y destino (desde C++20). Si se especifica el tamaño del array y es mayor que la cantidad de caracteres en el literal de cadena, los caracteres restantes se inicializan en cero.
char a[] = "abc"; // equivalente a char a[4] = {'a', 'b', 'c', '\0'}; // unsigned char b[3] = "abc"; // ERROR: cadena de inicializadores demasiado larga unsigned char b[5]{"abc"}; // equivalente a unsigned char b[5] = {'a', 'b', 'c', '\0', '\0'}; wchar_t c[] = {L"кошка"}; // llaves son opcionales // equivalente a wchar_t c[6] = {L'к', L'о', L'ш', L'к', L'а', L'\0'};
[editar] Notas
Una clase o array agregado puede incluir clases base públicas (desde C++17), miembros o elementos no agregados, que se inicializan como se describió anteriormente (por ejemplo, inicialización de copia desde la cláusula de inicialización correspondiente).
Hasta C++11, se permitían las conversiones de estrechamiento en la inicialización agregada, pero ya no se permiten.
Hasta C++11, la inicialización agregada solo se podía utilizar en la definición de variables y no se podía utilizar en una lista de inicializadores de constructores, una expresión new o la creación de objetos temporales debido a restricciones de sintaxis.
En C, un array de caracteres de tamaño uno menor que el tamaño del literal de cadena se puede inicializar desde un literal de cadena; el array resultante no termina en nulo. Esto no está permitido en C++.
Prueba de característica | Valor | Estándar | Comentario |
---|---|---|---|
__cpp_aggregate_bases |
201603L | (C++17) | Clases agregado con clases base. |
__cpp_aggregate_nsdmi |
201304L | (C++14) | Clases agregado con inicializadores de miembro por defecto. |
__cpp_aggregate_paren_init |
201902L | (C++20) | Inicialización de agregados en la forma de inicialización directa. |
__cpp_char8_t |
202207L | (C++20) | La corrección de compatibilidad y portabilidad char8_t (permite la inicialización de (unsigned char arrays a partir de literales de cadena UTF-8) |
__cpp_designated_initializers |
201707L | (C++20) | Inicializadores designados |
[editar] Ejemplo
#include <array> #include <cstdio> #include <string> struct S { int x; struct Foo { int i; int j; int a[3]; } b; }; int main() { S s1 = {1, {2, 3, {4, 5, 6}}}; S s2 = {1, 2, 3, 4, 5, 6}; // lo mismo, pero con elisión de llaves S s3{1, {2, 3, {4, 5, 6}}}; // lo mismo, usando sintaxis de inicialización de lista directa S s4{1, 2, 3, 4, 5, 6}; // ERROR hasta CWG 1270: // elisión de llaves solo se permite con el signo igual int ar[] = {1, 2, 3}; // ar es int[3] // char cr[3] = {'a', 'b', 'c', 'd'}; // demasiadas cláusulas de inicialización char cr[3] = {'a'}; // array inicializado como {'a', '\0', '\0'} int ar2d1[2][2] = {{1, 2}, {3, 4}}; // array 2D array con llaves: {1, 2} // {3, 4} int ar2d2[2][2] = {1, 2, 3, 4}; // elisión de llaves: {1, 2} // {3, 4} int ar2d3[2][2] = {{1}, {2}}; // solo primera columna: {1, 0} // {2, 0} std::array<int, 3> std_ar2{{1, 2, 3}}; // std::array es un agregado std::array<int, 3> std_ar1 = {1, 2, 3}; // elisión de llaves está bien // int ai[] = {1, 2.0}; // conversión de estrechamiento de double a int // ERROR en C++11, está bien en C++03 std::string ars[] = {std::string("one"), // inicialización de copia "two", // conversiónn, luego inicialización de copia {'t', 'h', 'r', 'e', 'e'}}; // inicialización de lista union U { int a; const char* b; }; U u1 = {1}; // de acuerdo, primer miembro de la unión // U u2 = {0, "asdf"}; // ERROR: demasiados inicializadores para la unión // U u3 = {"asdf"}; // ERROR: conversión no válida a int [](...) { std::puts("Recolección de basura de variables no utilizadas... Listo."); } ( s1, s2, s3, s4, ar, cr, ar2d1, ar2d2, ar2d3, std_ar2, std_ar1, u1 ); } // agregado struct base1 { int b1, b2 = 42; }; // no-agregado struct base2 { base2() : b3(42) {} int b3; }; // agregado en C++17 struct derived : base1, base2 { int d; }; derived d1{{1, 2}, {}, 4}; // d1.b1 = 1, d1.b2 = 2, d1.b3 = 42, d1.d = 4 derived d2{{}, {}, 4}; // d2.b1 = 0, d2.b2 = 42, d2.b3 = 42, d2.d = 4
Salida:
Recolección de basura de variables no utilizadas... Listo.
[editar] Informes de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
---|---|---|---|
CWG 413 | C++98 | Los campos de bits anónimos se inicializaron en la inicialización de agregado. | Se ignoran. |
CWG 737 | C++98 | Cuando un array de caracteres se inicializa con un literal de cadena que tiene menos caracteres que el tamaño del array, los elementos de caracteres después del '\0' final no se inicializaron. |
se inicializan a cero. |
CWG 1270 | C++11 | La elisión de llaves solo se podía usar en la inicialización lista de copia. | Se permite en otros lugares. |
CWG 1518 | C++11 | Una clase que declara un constructor explícito por defecto o constructores heredados podría ser un agregado. |
No es un agregado. |
CWG 1622 | C++98 | No se podía inicializar una unión con {}. | Se permite. |
P3106R1 | C++98 | No estaba claro si la elisión de llaves era aplicable durante la deducción del tamaño del array. |
Aplicable. |
CWG 2272 | C++98 | Un miembro de referencia no estático que no está explícitamente inicializado se inicializa por copia desde una lista de inicializadores vacía. |
El programa está mal formado en este caso. |
CWG 2610 | C++17 | Los tipos agregado no podían tener clases base indirectas privadas o protegidas. | Se permite. |
CWG 2619 | C++20 | El tipo de inicialización de los inicializadores designados no estaba claro. | depende del tipo de inicializador. |
P2513R4 | C++20 | Un literal de cadena UTF-8 no podía inicializar un array de char o unsigned char, que era incompatible con C o C++17.7 |
Tal inicialización es válida. |