Espacios de nombres
Variantes
Acciones

Inicialización de agregado

De cppreference.com
< cpp‎ | language

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)
1,2) Inicialización de un agregado con una lista de inicializadores normal.
3,4) Inicialización de un agregado con inicializadores designados (solo clase agregado).

[editar] Definiciones

[editar] Agregado

Un agregado es uno de los siguientes tipos:

  • tipos array;
  • tipos clase que no tienen:
  • constructores declarados por el usuario
(hasta C++11)
(desde C++11)
(hasta C++20)
  • constructores declarados por el usuario o heredados
(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
  • para una clase, los datos miembro no estáticos que no son campos de bits anónimos, en orden de declaración.
(hasta C++17)
  • para una clase, las clases base directas en orden de declaración, seguidas de los datos miembro no estáticos directos que no son campos de bits anónimos ni miembros de una unión anónima, en orden de declaración.
(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:

1) Determina los elementos inicializados explícitamente del agregado de la siguiente manera:
  • Si la lista de inicializadores es una lista de inicializadores designados (el agregado solo puede ser de tipo clase), el identificador en cada designador debe nombrar un dato miembro no estático directo de la clase, y los elementos inicializados explícitamente del agregado son los elementos que son, o contienen, esos miembros.
(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
2) Inicializa cada elemento del agregado en el orden de los elementos. Es decir, todos los cálculos de valores y efectos secundarios asociados con un elemento dado se secuencian antes de aquellos de cualquier elemento que lo siga en orden (desde C++11).

[editar] Elementos inicializados explícitamente

Para cada elemento inicializado explícitamente:

  • Si el elemento es un miembro de unión anónimo y la lista de inicializadores es una lista de inicializadores designados, el elemento es inicializado por la lista de inicializadores designados {D}, donde D es la cláusula de inicializador designado que nombra a un miembro del miembro de unión anónimo. Solo debe haber una tal cláusula de inicializador designado.
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
  • De lo contrario, si la lista de inicializadores es una lista de inicializadores designados, el elemento se inicializa con el inicializador de la cláusula de inicializador designado correspondiente.
  • Si ese inicializador es de sintaxis (1), y se requiere una conversión de estrechamiento para convertir la expresión, el programa está mal formado.
(desde C++20)


  • La lista de inicializadores es una lista de inicializadores encerrada entre llaves:
(hasta C++20)
  • De lo contrario, la lista de inicializadores es una lista de inicializadores encerrada entre llaves no designada:
(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

  • Si algún miembro variante tiene un inicializador de miembro por defecto, ese miembro se inicializa desde su inicializador de miembro por defecto.
(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 designados

Las 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.

[editar] Véase también