Inicialización de estructura C ++


279

¿Es posible inicializar estructuras en C ++ como se indica a continuación?

struct address {
    int street_no;
    char *street_name;
    char *city;
    char *prov;
    char *postal_code;
};
address temp_address =
    { .city = "Hamilton", .prov = "Ontario" };

Los enlaces aquí y aquí mencionan que es posible usar este estilo solo en C. Si es así, ¿por qué esto no es posible en C ++? ¿Hay alguna razón técnica subyacente por la que no se implementa en C ++, o es una mala práctica usar este estilo? Me gusta usar esta forma de inicialización porque mi estructura es grande y este estilo me da una clara legibilidad de qué valor se asigna a cada miembro.

Comparta conmigo si hay otras formas a través de las cuales podemos lograr la misma legibilidad.

He referido los siguientes enlaces antes de publicar esta pregunta

  1. C / C ++ para AIX
  2. C Estructura de inicialización con variable
  3. Inicialización de estructura estática con etiquetas en C ++
  4. Inicialización de estructura adecuada de C ++ 11

20
Visión personal del mundo: no necesita este estilo de inicialización de objetos en C ++ porque debería usar un constructor en su lugar.
Philip Kendall

77
Sí, pensé en eso, pero tengo una gran variedad de grandes estructuras. Sería fácil y legible para mí usarlo de esta manera. ¿Tiene algún estilo / buena práctica de inicializar usando Constructor que también le da una mejor legibilidad?
Dinesh PR

2
¿Has considerado la biblioteca de parámetros de impulso, en combinación con los constructores? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Tony Delroy

18
No tan relacionado con la programación: esta dirección funciona bien solo en los EE. UU. En Francia, no tenemos una "provincia", en otras partes del mundo, no hay código postal, la abuela de una amiga vive en un pueblo tan pequeño que su dirección es "Ms X, código postal nombre-pequeño-pueblo "(sí, sin calle). Así que considere cuidadosamente qué dirección válida es para el mercado al que aplicará esto;)
Matthieu M.

55
@MatthieuM. No hay provincias en los Estados Unidos (¿este podría ser un formato canadiense?), Pero hay estados, territorios e incluso pequeñas aldeas que no se molestan en nombrar calles. Por lo tanto, el problema de la conformidad de la dirección se aplica incluso aquí.
Tim

Respuestas:


167

Si desea dejar en claro cuál es el valor de cada inicializador, simplemente divídalo en varias líneas, con un comentario en cada una:

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};

77
Personalmente me gusta y recomiendo este estilo
Dinesh PR

30
¿Cuál es la diferencia entre hacer eso y usar la notación de puntos para acceder MÁS CON EXACTITUD al campo en sí mismo? No es como si estuviera ahorrando espacio si esa es la preocupación. Realmente no entiendo a los programadores de C ++ cuando se trata de ser coherente y escribir código mantenible, parece que siempre quieren hacer algo diferente para que su código se destaque, el código está destinado a reflejar el problema que se está resolviendo, no debería ser un idioma en sí mismo, apunta a la confiabilidad y facilidad de mantenimiento.

55
@ user1043000 bien, por ejemplo, en este caso el orden en que coloca a sus miembros es de suma importancia. Si agrega un campo en el medio de su estructura, tendrá que volver a este código y buscar el lugar exacto en el que insertar su nueva inicialización, que es difícil y aburrido. Con la notación de puntos, simplemente puede colocar su nueva inicialización al final de la lista sin preocuparse por el orden. Y la notación de puntos es mucho más segura si agrega el mismo tipo (como char*) como uno de los otros miembros arriba o abajo en la estructura, porque no hay riesgo de intercambiarlos.
Gui13

66
comentario de orip. Si se cambia la definición de la estructura de datos, y nadie piensa en buscar las inicializaciones, o no puede encontrarlas todas, o comete un error al editarlas, las cosas se vendrán abajo.
Edward Falk

3
La mayoría de las estructuras POSIX (si no todas) no tienen un orden definido, solo miembros definidos. (struct timeval){ .seconds = 0, .microseconds = 100 }siempre será cien microsegundos, pero timeval { 0, 100 }podría ser cien SEGUNDOS . No querrás encontrar algo así por las malas.
yyny

99

Después de que mi pregunta no resultó en un resultado satisfactorio (porque C ++ no implementa init basado en etiquetas para estructuras), tomé el truco que encontré aquí: ¿Los miembros de una estructura C ++ se inicializan a 0 de manera predeterminada?

Para ti equivaldría a hacer eso:

address temp_address = {}; // will zero all fields in C++
temp_address.city = "Hamilton";
temp_address.prov = "Ontario";

Este es sin duda el más cercano a lo que quería originalmente (cero todos los campos excepto los que desea inicializar).


10
Esto no funciona para objetos no inicializados estáticamente
user877329

55
static address temp_address = {};trabajará. Llenarlo después depende del tiempo de ejecución, sí. Puede pasar por alto esto proporcionando una función estática que hace el init para usted: static address temp_address = init_my_temp_address();.
Gui13

En C ++ 11, init_my_temp_addresspuede ser una función lambda:static address temp_address = [] () { /* initialization code */ }();
dureuill

3
Mala idea, viola el principio RAII.
hxpax

2
Realmente mala idea: agregue un miembro a su addressy nunca sabrá de todos los lugares que crean addressy ahora no inicializa su nuevo miembro.
mystery_doctor


17

Los identificadores de campo son de hecho sintaxis de inicializador C. En C ++ solo da los valores en el orden correcto sin los nombres de campo. Desafortunadamente, esto significa que debe darlos a todos (en realidad, puede omitir los campos finales de valor cero y el resultado será el mismo):

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 }; 

1
Sí, siempre puede usar la inicialización de estructura alineada.
texasbruce

3
Sí, actualmente solo estoy usando este método (Inicialización de estructura alineada). Pero siento que la legibilidad no es buena. Como mi Estructura es grande, el inicializador tiene tantos datos y me resulta difícil rastrear qué valor se asigna a cada miembro.
Dinesh PR

77
@ DineshP.R. ¡Entonces escribe un constructor!
Sr. Lister

2
@MrLister (o cualquiera) ¿Quizás estoy atrapado en una nube de estupidez en este momento, pero me gustaría explicar cómo un constructor sería mucho mejor? Me parece que hay poca diferencia entre proporcionar un grupo de valores sin nombre dependientes del orden a una lista de inicializadores o proporcionar un grupo de valores sin nombre dependientes del orden a un constructor ...?
yano

1
@yano Para ser honesto, realmente no recuerdo por qué pensé que un constructor sería la respuesta al problema. Si no recuerdo mal, volveré contigo.
Sr. Lister el

13

Esta característica se llama inicializadores designados . Es una adición al estándar C99. Sin embargo, esta característica quedó fuera de C ++ 11. De acuerdo con el lenguaje de programación C ++, 4a edición, Sección 44.3.3.2 (Características C no adoptadas por C ++):

Algunas adiciones a C99 (en comparación con C89) no se adoptaron deliberadamente en C ++:

[1] Conjuntos de longitud variable (VLA); usar vector o alguna forma de matriz dinámica

[2] Inicializadores designados; usar constructores

La gramática C99 tiene los inicializadores designados [Ver ISO / IEC 9899: 2011, Borrador del Comité N1570 - 12 de abril de 2011]

6.7.9 Inicialización

initializer:
    assignment-expression
    { initializer-list }
    { initializer-list , }
initializer-list:
    designation_opt initializer
    initializer-list , designationopt initializer
designation:
    designator-list =
designator-list:
    designator
    designator-list designator
designator:
    [ constant-expression ]
    . identifier

Por otro lado, el C ++ 11 no tiene los inicializadores designados [Ver ISO / IEC 14882: 2011, Borrador del Comité N3690 - 15 de mayo de 2013]

8.5 Inicializadores

initializer:
    brace-or-equal-initializer
    ( expression-list )
brace-or-equal-initializer:
    = initializer-clause
    braced-init-list
initializer-clause:
    assignment-expression
    braced-init-list
initializer-list:
    initializer-clause ...opt
    initializer-list , initializer-clause ...opt
braced-init-list:
    { initializer-list ,opt }
    { }

Para lograr el mismo efecto, use constructores o listas de inicializadores:


9

Simplemente puede inicializar a través de ctor:

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};

9
Este es el caso solo si controla la definición de struct address. Además, los tipos de POD a menudo intencionalmente no tienen constructor ni destructor.
user4815162342

7

Incluso puede empaquetar la solución de Gui13 en una sola declaración de inicialización:

struct address {
                 int street_no;
                 char *street_name;
                 char *city;
                 char *prov;
                 char *postal_code;
               };


address ta = (ta = address(), ta.city = "Hamilton", ta.prov = "Ontario", ta);

Descargo de responsabilidad: no recomiendo este estilo


Esto sigue siendo peligroso porque le permite agregar un miembro addressy el código aún se compilará con un millón de lugares solo inicializando los cinco miembros originales. La mejor parte de la inicialización de la estructura es que puedes tener todos los miembros consty te obligará a inicializarlos a todos
mystery_doctor

7

Sé que esta pregunta es bastante antigua, pero encontré otra forma de inicializar, usando constexpr y curry:

struct mp_struct_t {
    public:
        constexpr mp_struct_t(int member1) : mp_struct_t(member1, 0, 0) {}
        constexpr mp_struct_t(int member1, int member2, int member3) : member1(member1), member2(member2), member3(member3) {}
        constexpr mp_struct_t another_member(int member) { return {member1, member,     member2}; }
        constexpr mp_struct_t yet_another_one(int member) { return {member1, member2, member}; }

    int member1, member2, member3;
};

static mp_struct_t a_struct = mp_struct_t{1}
                           .another_member(2)
                           .yet_another_one(3);

Este método también funciona para variables globales estáticas e incluso constexpr. La única desventaja es la mala capacidad de mantenimiento: cada vez que se debe inicializar otro miembro utilizando este método, se deben cambiar todos los métodos de inicialización del miembro.


1
Este es el patrón de construcción . Los métodos miembro pueden devolver una referencia a la propiedad que se va a modificar en lugar de crear una nueva estructura cada vez
phuclv

6

Podría estar perdiendo algo aquí, por qué no:

#include <cstdio>    
struct Group {
    int x;
    int y;
    const char* s;
};

int main() 
{  
  Group group {
    .x = 1, 
    .y = 2, 
    .s = "Hello it works"
  };
  printf("%d, %d, %s", group.x, group.y, group.s);
}

Compilé el programa anterior con un compilador MinGW C ++ y un compilador Arduino AVR C ++, y ambos se ejecutaron como se esperaba. Observe el #include <cstdio>
run_the_race

44
@run_the_race, se trata de lo que dice el estándar de c ++, no del comportamiento de un compilador determinado. Sin embargo, esta característica viene en c ++ 20.
Jon

esto solo funciona si la estructura es POD. Por lo tanto, dejará de compilarse si le agrega un constructor.
oromoiluig

5

No está implementado en C ++. (también, char*cadenas? Espero que no).

Por lo general, si tiene tantos parámetros, es un olor a código bastante serio. Pero, en cambio, ¿por qué no simplemente inicializar el valor de la estructura y luego asignar a cada miembro?


66
"(también, char*¿cuerdas? Espero que no)". - Bueno, es un ejemplo de C.
Ed S.

¿No podemos usar char * en C ++? Actualmente lo estoy usando y está funcionando (puede que esté haciendo algo mal). Mi suposición es que el compilador creará cadenas constantes de "Hamilton" y "Ontario" y asignará su dirección a los miembros de la estructura. ¿Será correcto usar const char * en su lugar?
Dinesh PR

8
Puede usarlo, char*pero const char*es mucho más seguro y todo el mundo lo usa std::stringporque es mucho más confiable.
Cachorro

Okay. Cuando leí "como se menciona a continuación" supuse que era un ejemplo copiado de alguna parte.
Ed S.

2

Encontré esta forma de hacerlo para las variables globales, que no requiere modificar la definición de estructura original:

struct address {
             int street_no;
             char *street_name;
             char *city;
             char *prov;
             char *postal_code;
           };

luego declare la variable de un nuevo tipo heredado del tipo de estructura original y use el constructor para la inicialización de campos:

struct temp_address : address { temp_address() { 
    city = "Hamilton"; 
    prov = "Ontario"; 
} } temp_address;

Aunque no es tan elegante como el estilo C ...

Para una variable local, requiere un memset adicional (this, 0, sizeof (* this)) al comienzo del constructor, por lo que claramente no es peor y la respuesta de @ gui13 es más apropiada.

(Tenga en cuenta que 'temp_address' es una variable de tipo 'temp_address', sin embargo, este nuevo tipo hereda de 'address' y se puede usar en todos los lugares donde se espera 'address', por lo que está bien).


1

Hoy me enfrenté a un problema similar, donde tengo una estructura que quiero llenar con datos de prueba que se pasarán como argumentos a una función que estoy probando. Quería tener un vector de estas estructuras y estaba buscando un método de una línea para inicializar cada estructura.

Terminé yendo con una función de constructor en la estructura, que creo que también se sugirió en algunas respuestas a su pregunta.

Probablemente sea una mala práctica hacer que los argumentos para el constructor tengan los mismos nombres que las variables miembro públicas, lo que requiere el uso del thispuntero. Alguien puede sugerir una edición si hay una mejor manera.

typedef struct testdatum_s {
    public:
    std::string argument1;
    std::string argument2;
    std::string argument3;
    std::string argument4;
    int count;

    testdatum_s (
        std::string argument1,
        std::string argument2,
        std::string argument3,
        std::string argument4,
        int count)
    {
        this->rotation = argument1;
        this->tstamp = argument2;
        this->auth = argument3;
        this->answer = argument4;
        this->count = count;
    }

} testdatum;

Lo que usé en mi función de prueba para llamar a la función que se está probando con varios argumentos como este:

std::vector<testdatum> testdata;

testdata.push_back(testdatum("val11", "val12", "val13", "val14", 5));
testdata.push_back(testdatum("val21", "val22", "val23", "val24", 1));
testdata.push_back(testdatum("val31", "val32", "val33", "val34", 7));

for (std::vector<testdatum>::iterator i = testdata.begin(); i != testdata.end(); ++i) {
    function_in_test(i->argument1, i->argument2, i->argument3, i->argument4m i->count);
}

1

Es posible, pero solo si la estructura que está inicializando es una estructura POD (datos antiguos simples). No puede contener ningún método, constructor o incluso valores predeterminados.


1

En C ++, los inicializadores de estilo C fueron reemplazados por constructores que, en tiempo de compilación, pueden garantizar que solo se realicen inicializaciones válidas (es decir, después de la inicialización, los miembros del objeto son consistentes).

Es una buena práctica, pero a veces es útil una preinicialización, como en su ejemplo. OOP resuelve esto mediante clases abstractas o patrones de diseño de creación .

En mi opinión, usar esta forma segura mata la simplicidad y, a veces, la compensación de seguridad puede ser demasiado costosa, ya que el código simple no necesita un diseño sofisticado para mantenerse mantenible.

Como solución alternativa, sugiero definir macros usando lambdas para simplificar la inicialización para que se vea casi como el estilo C:

struct address {
  int street_no;
  const char *street_name;
  const char *city;
  const char *prov;
  const char *postal_code;
};
#define ADDRESS_OPEN [] { address _={};
#define ADDRESS_CLOSE ; return _; }()
#define ADDRESS(x) ADDRESS_OPEN x ADDRESS_CLOSE

La macro DIRECCIÓN se expande a

[] { address _={}; /* definition... */ ; return _; }()

que crea y llama a la lambda. Los parámetros de macro también están separados por comas, por lo que debe poner entre paréntesis el inicializador y llamar como

address temp_address = ADDRESS(( _.city = "Hamilton", _.prov = "Ontario" ));

También podría escribir macro inicializador generalizado

#define INIT_OPEN(type) [] { type _={};
#define INIT_CLOSE ; return _; }()
#define INIT(type,x) INIT_OPEN(type) x INIT_CLOSE

pero entonces la llamada es un poco menos hermosa

address temp_address = INIT(address,( _.city = "Hamilton", _.prov = "Ontario" ));

sin embargo, puede definir la macro DIRECCIÓN usando la macro INIT general fácilmente

#define ADDRESS(x) INIT(address,x)


0

Inspirado por esta respuesta realmente ordenada: ( https://stackoverflow.com/a/49572324/4808079 )

Puedes hacer cierres de lamba:

// Nobody wants to remember the order of these things
struct SomeBigStruct {
  int min = 1;
  int mean = 3 ;
  int mode = 5;
  int max = 10;
  string name;
  string nickname;
  ... // the list goes on
}

.

class SomeClass {
  static const inline SomeBigStruct voiceAmps = []{
    ModulationTarget $ {};
    $.min = 0;  
    $.nickname = "Bobby";
    $.bloodtype = "O-";
    return $;
  }();
}

O, si quieres ser muy elegante

#define DesignatedInit(T, ...)\
  []{ T ${}; __VA_ARGS__; return $; }()

class SomeClass {
  static const inline SomeBigStruct voiceAmps = DesignatedInit(
    ModulationTarget,
    $.min = 0,
    $.nickname = "Bobby",
    $.bloodtype = "O-",
  );
}

Hay algunos inconvenientes relacionados con esto, principalmente relacionados con miembros no iniciados. Por lo que dicen los comentarios de las respuestas vinculadas, se compila de manera eficiente, aunque no lo he probado.

En general, creo que es un enfoque ordenado.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.