Aggregate initialization
Inicializa un agregado desde una lista de inicialización . Es una forma de list-initialization (since C++11) .
Contenidos |
Sintaxis
T objeto
= {
arg1, arg2, ...
};
|
(1) | ||||||||
T objeto
{
arg1, arg2, ...
};
|
(2) | (desde C++11) | |||||||
T objeto
= { .
des1
=
arg1
, .
des2
{
arg2
}
...
};
|
(3) | (desde C++20) | |||||||
T objeto
{ .
des1
=
arg1
, .
des2
{
arg2
}
...
};
|
(4) | (desde C++20) | |||||||
Definiciones
Agregado
Un aggregate es uno de los siguientes tipos:
- tipos de array
- tipos de clase que tiene
|
(hasta C++11) |
|
(desde C++11)
(hasta C++20) |
|
|
(desde C++20) |
-
- no hay miembros de datos no estáticos directos privados o protegidos
|
(hasta C++17) |
|
(desde C++17) |
-
- sin funciones miembro virtuales
|
(desde C++11)
(hasta C++14) |
Elemento
Los elementos de un agregado son:
- para un array, los elementos del array en orden creciente de subíndice, o
|
(until C++17) |
|
(since C++17) |
Pertenencia
Cada cláusula de inicialización en una lista de inicialización entre llaves se dice que corresponde a un elemento del agregado que se está inicializando o a un elemento de uno de sus subagregados.
Considerando la secuencia de cláusulas de inicialización, y la secuencia de elementos del agregado 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 satisface alguna de las siguientes condiciones, corresponde al elemento agregado correspondiente elem :
-
- elem no es un agregado.
- La cláusula de inicialización comienza con { .
- La cláusula de inicialización 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 a su vez no tiene elementos agregados.
- De lo contrario, elem es un agregado y ese subagregado se reemplaza en la lista de elementos del agregado por la secuencia de sus propios elementos agregados, y el análisis de pertenencia se reanuda con el primer elemento de este tipo y la misma cláusula de inicialización. 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 de inicialización. Si queda alguna cláusula de inicialización que no corresponda a un elemento del agregado o a uno de sus subagregados, el programa está mal formado.
struct S1 { long a, b; }; struct S2 { S1 s, t; }; // Cada subagregado de “x” se asigna a una cláusula de inicialización que comienza con { S2 x[2] = { // se asigna a “x[0]” { {1L, 2L}, // se asigna a “x[0].s” {3L, 4L} // se asigna a “x[0].t” }, // se asigna a “x[1]” { {5L, 6L}, // se asigna a “x[1].s” {7L, 8L} // se asigna a “x[1].t” } }; // “x” e “y” tienen el mismo valor (ver abajo) S2 y[2] = {1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L}; // El proceso del análisis de asignación de “y”: // 1. Inicializa la secuencia de elementos agregados (x[0], x[1]) y // la secuencia de cláusulas de inicialización (1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L). // 2. Comenzando desde los primeros elementos de cada secuencia, // verifica si 1L se asigna a x[0]: // · x[0] es un agregado. // · 1L no comienza con {. // · 1L es una expresión, pero no puede convertirse implícitamente a S2. // · x[0] tiene elementos agregados. // 3. 1L no puede asignarse 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 verificación de asignación, pero 1L tampoco puede asignarse 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 la verificación de asignación nuevamente: // 1L se asigna a x[0].s.a, y 2L se asigna a x[0].s.b. // 7. El resto del análisis de asignación funciona de manera similar. char cv[4] = {'a', 's', 'd', 'f', 0}; // Error: demasiadas cláusulas de inicialización
Proceso de inicialización
Determinación del tipo de elemento
Los efectos de la inicialización de agregados son:
|
(desde C++20) |
-
- De lo contrario, (desde C++20) si la lista de inicialización no está vacía, los elementos explícitamente inicializados del agregado son los elementos con una cláusula de inicialización correspondiente y los elementos que tienen un subagregado con una cláusula de inicialización correspondiente.
- De lo contrario, la lista de inicialización debe estar vacía ( { } ), y no hay elementos explícitamente inicializados.
- El programa está mal formado si el agregado es una unión y hay dos o más elementos inicializados explícitamente:
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" // C++20 listas de inicializadores designados 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
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) |
|
(until C++20) |
|
(since C++20) |
-
- Si una cláusula inicializadora corresponde al elemento del agregado, entonces el elemento del agregado es copy-initialized desde la cláusula inicializadora.
- De lo contrario, el elemento del agregado es copy-initialized desde una lista inicializadora entre llaves que consiste en todas las cláusulas inicializadoras que corresponden a subobjetos del elemento del agregado, en 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
Elementos inicializados implícitamente
Para un agregado no unión, cada elemento que no es un elemento explícitamente inicializado se inicializa de la siguiente manera:
|
(since C++11) |
- De lo contrario, si el elemento no es una referencia, el elemento es copy-initialized desde 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 una unión y la lista de inicializadores está vacía, entonces
|
(since C++11) |
- De lo contrario, el primer miembro de la unión (si existe) se inicializa por copia desde una lista de inicializadores vacía.
Arreglos con límites desconocidos
El número de elementos en un arreglo de límite desconocido inicializado con una lista de inicialización entre llaves es el número de elementos explícitamente inicializados del arreglo. Un arreglo de límite desconocido no puede ser inicializado 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 tiene solo 2 elementos: // 1, 2 y 3 corresponden a y[0], // 4, 5 y 6 corresponden a y[1] int z[] = {} // Error: no se puede declarar un array sin elementos
Inicializadores designadosLas formas de sintaxis (3,4) se conocen como inicializadores designados: cada designador debe nombrar un miembro de datos directo no estático de T, y todos los designadores utilizados en la expresión deben aparecer en el mismo orden que los miembros de datos de T. struct A { int x; int y; int z; }; A a{.x = 1, .y = 2, .z = 3}; // ok A b{.y = 2, .z = 3, .x = 1}; // error; designator order does not match declaration order Cada miembro de datos directo no estático nombrado por el inicializador designado se inicializa desde el inicializador entre llaves o igual correspondiente que sigue al designador. Se prohíben las conversiones que reduzcan el rango. El inicializador designado puede usarse para inicializar una union en un estado diferente al primero. Solo se puede proporcionar un inicializador para una union. union u { int a; const char* b; }; u f = {.b = "asdf"}; // OK, active member of the union is b u g = {.a = 1, .b = "asdf"}; // Error, only one initializer may be provided Para un agregado no union, los elementos para los cuales no se proporciona un inicializador designado se inicializan de la misma manera descrita anteriormente para cuando el número de cláusulas de inicialización es menor que el número de miembros (inicializadores de miembros predeterminados donde se proporcionaron, inicialización de lista vacía en caso contrario): struct A { string str; int n = 42; int m = -1; }; A{.m = 21} // Initializes str with {}, which calls the default constructor // then initializes n with = 42 // then initializes m with = 21 struct A { int x; int y; int z; }; A a{.x = 1, .z = 2}; // ok, b.y initialized to 0 A b{.y = 2, .x = 1}; // error; designator order does not match declaration order A c{.y = 2}; // ok, c.x and c.z are initialized to 0 constexpr A d{.z = 2}; // can be used with constexpr, as opposed to: constexpr A d; static_assert(d.x == 0 && d.y == 0); // d.x and d.y are initialized to 0 Si el agregado que se inicializa con una cláusula de inicializador designado tiene un miembro union anónimo, el inicializador designado correspondiente debe nombrar uno de los miembros de esa union anónima. Nota: la inicialización designada fuera de orden, la inicialización designada anidada, la mezcla de inicializadores designados e inicializadores regulares, y la inicialización designada de arrays están todas soportadas en el lenguaje de programación C , pero no están permitidas en C++. struct A { int x, y; }; struct B { struct A a; }; struct A a = {.y = 1, .x = 2}; // valid C, invalid C++ (out of order) int arr[3] = {[1] = 5}; // valid C, invalid C++ (array) struct B b = {.a.x = 0}; // valid C, invalid C++ (nested) struct A a = {.x = 1, 2}; // valid C, invalid C++ (mixed) |
(desde C++20) |
Arreglos de caracteres
Los arreglos de tipos de caracteres ordinarios ( char , signed char , unsigned char ) , char8_t (desde C++20) , char16_t , char32_t (desde C++11) , o wchar_t pueden inicializarse desde 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) , respectivamente, opcionalmente encerrados entre llaves . Adicionalmente, un arreglo de char o unsigned char puede ser inicializado por un literal de cadena UTF-8, opcionalmente encerrado entre llaves (desde C++20) . Los caracteres sucesivos del literal de cadena (que incluye el carácter nulo terminador implícito) inicializan los elementos del arreglo , con una conversión integral si es necesario para el valor fuente y destino (desde C++20) . Si se especifica el tamaño del arreglo y es mayor que el número de caracteres en el literal de cadena, los caracteres restantes se inicializan a cero.
char a[] = "abc"; // equivalente a char a[4] = {'a', 'b', 'c', '\0'}; // unsigned char b[3] = "abc"; // Error: cadena inicializadora demasiado larga unsigned char b[5]{"abc"}; // equivalente a unsigned char b[5] = {'a', 'b', 'c', '\0', '\0'}; wchar_t c[] = {L"кошка"}; // llaves opcionales // equivalente a wchar_t c[6] = {L'к', L'о', L'ш', L'к', L'а', L'\0'};
Notas
Una clase o arreglo agregado puede incluir bases públicas no agregadas (desde C++17) , miembros o elementos, que se inicializan como se describió anteriormente (por ejemplo, inicialización por copia desde la cláusula inicializadora correspondiente).
Hasta C++11, se permitían conversiones de estrechamiento en la inicialización de agregados, pero ya no están permitidas.
Hasta C++11, la inicialización de agregados solo podía utilizarse en la definición de variables, y no podía usarse en una lista de inicialización de constructores , una expresión new , o la creación de objetos temporales debido a restricciones de sintaxis.
En C, un arreglo de caracteres de tamaño uno menos que el tamaño del literal de cadena puede ser inicializado desde un literal de cadena; el arreglo resultante no está terminado en nulo. Esto no está permitido en C++.
| Macro de prueba de características | Valor | Estándar | Característica |
|---|---|---|---|
__cpp_aggregate_bases
|
201603L
|
(C++17) | Clases agregadas con clases base |
__cpp_aggregate_nsdmi
|
201304L
|
(C++14) | Clases agregadas con inicializadores de miembros predeterminados |
__cpp_aggregate_paren_init
|
201902L
|
(C++20) | Inicialización agregada en forma de inicialización directa |
__cpp_char8_t
|
202207L
|
(C++23)
(DR20) |
char8_t corrección de compatibilidad y portabilidad ( permitir inicialización de arrays ( unsigned char ) desde literales de cadena UTF-8 ) |
__cpp_designated_initializers
|
201707L
|
(C++20) | Inicializadores designados |
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}; // igual, pero con elisión de llaves S s3{1, {2, 3, {4, 5, 6}}}; // igual, usando sintaxis de inicialización directa por lista S s4{1, 2, 3, 4, 5, 6}; // error hasta CWG 1270: // la elisión de llaves solo se permite con signo igual int ar[] = {1, 2, 3}; // ar es int[3] // char cr[3] = {'a', 'b', 'c', 'd'}; // demasiadas cláusulas inicializadoras char cr[3] = {'a'}; // array inicializado como {'a', '\0', '\0'} int ar2d1[2][2] = {{1, 2}, {3, 4}}; // array 2D completamente entre 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 correcta // int ai[] = {1, 2.0}; // conversión estrechante de double a int: // error en C++11, correcto en C++03 std::string ars[] = {std::string("one"), // inicialización por copia "two", // conversión, luego inicialización por copia {'t', 'h', 'r', 'e', 'e'}}; // inicialización por lista union U { int a; const char* b; }; U u1 = {1}; // OK, primer miembro de la unión // U u2 = {0, "asdf"}; // error: demasiados inicializadores para unión // U u3 = {"asdf"}; // error: conversión inválida a int [](...) { std::puts("Garbage collecting unused variables... Done."); } ( 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:
Garbage collecting unused variables... Done.
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
| DR | Se aplica a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 413 | C++98 | los campos de bits anónimos se inicializaban en la inicialización de agregados | 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 permanecían sin inicializar |
se inicializan
a cero |
| CWG 1270 | C++11 | la elisión de llaves solo estaba permitida en la inicialización de lista de copia | permitida en otros contextos |
| CWG 1518 | C++11 |
una clase que declara un constructor por defecto explícito o
tiene constructores heredados podría ser un agregado |
no es un
agregado |
| CWG 1622 | C++98 | una unión no podía inicializarse con { } | permitido |
|
CWG 2149
( P3106R1 ) |
C++98 |
no estaba claro si la elisión de llaves es
aplicable durante la deducción del tamaño del array |
aplicable |
| CWG 2272 | C++98 |
un miembro de referencia no estático que no se inicializa
explícitamente se inicializaba por copia desde una lista de inicializadores vacía |
el programa está
mal formado en este caso |
| CWG 2610 | C++17 | los tipos agregados no podían tener clases base indirectas privadas o protegidas | permitido |
| CWG 2619 | C++20 | el tipo de inicialización desde inicializadores designados no estaba claro |
depende del
tipo del inicializador |
| P2513R4 | C++20 |
un literal de cadena UTF-8 no podía inicializar un array de
char
o unsigned char , lo que era incompatible con C o C++17 |
dicha inicialización
es válida |
Véase también
|
Documentación de C
para
Inicialización de estructuras y uniones
|