Inizializzazione della struttura C ++


279

È possibile inizializzare le strutture in C ++ come indicato di seguito

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

I collegamenti qui e qui menzionano che è possibile utilizzare questo stile solo in C. In caso affermativo, perché non è possibile in C ++? C'è qualche motivo tecnico di fondo per cui non è implementato in C ++, o è una cattiva pratica usare questo stile? Mi piace usare questo modo di inizializzare perché la mia struttura è grande e questo stile mi dà una chiara leggibilità di quale valore è assegnato a quale membro.

Per favore condividi con me se ci sono altri modi in cui possiamo ottenere la stessa leggibilità.

Ho fatto riferimento ai seguenti link prima di pubblicare questa domanda

  1. C / C ++ per AIX
  2. Inizializzazione della struttura C con variabile
  3. Inizializzazione della struttura statica con tag in C ++
  4. C ++ 11 Inizializzazione della struttura corretta

20
Vista personale del mondo: non è necessario questo stile di inizializzazione degli oggetti in C ++ perché si dovrebbe usare un costruttore.
Philip Kendall,

7
Sì, ci ho pensato, ma ho una serie di grandi strutture. Sarebbe facile e leggibile per me usare in questo modo. Hai qualche stile / buona pratica di inizializzazione con Costruttore che offre anche una migliore leggibilità.
Dinesh PR,

2
Hai considerato la libreria di parametri boost, in combinazione con i costruttori? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Tony Delroy,

18
Non così legato alla programmazione: questo indirizzo funziona bene solo negli Stati Uniti. In Francia, non abbiamo una "provincia", in altre parti del mondo, non esiste un codice postale, una nonna di un amico vive in un villaggio così piccolo che il suo indirizzo è "Sig.ra X, codice postale nome di un piccolo villaggio "(sì, nessuna strada). Quindi considera attentamente a quale indirizzo valido è il mercato al quale lo applicherai;)
Matthieu M.

5
@MatthieuM. Non ci sono province negli Stati Uniti (questo potrebbe essere un formato canadese?), Ma ci sono stati, territori e persino minuscoli villaggi che non si preoccupano di nominare le strade. Quindi il problema della conformità degli indirizzi si applica anche qui.
Tim

Risposte:


167

Se vuoi chiarire qual è il valore di ciascun inizializzatore, basta dividerlo su più righe, con un commento su ciascuno:

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

7
Personalmente mi piace e consiglio questo stile
Dinesh PR,

30
Qual è la differenza tra farlo e usare effettivamente la notazione a punti per accedere PIÙ ACCURATAMENTE al campo stesso, non è come se stessi risparmiando spazio se è questo il problema. Davvero non capisco i programmatori C ++ quando si tratta di essere coerenti e scrivere codice gestibile, sembrano voler sempre fare qualcosa di diverso per far risaltare il loro codice, il codice è pensato per riflettere il problema da risolvere, non dovrebbe essere un linguaggio a sé stante, mirano all'affidabilità e alla facilità di manutenzione.

5
@ user1043000 bene, per esempio, in questo caso l'ordine in cui metti i tuoi membri è della massima importanza. Se aggiungi un campo nel mezzo della tua struttura, dovrai tornare a questo codice e cercare il punto esatto in cui inserire la tua nuova inizializzazione, che è difficile e noioso. Con la notazione punto, puoi semplicemente inserire la nuova inizializzazione alla fine dell'elenco senza preoccuparti dell'ordine. E la notazione punto è molto più sicura se ti capita di aggiungere lo stesso tipo (come char*) di uno degli altri membri sopra o sotto nella struttura, perché non c'è rischio di scambiarli.
Gui13

6
commento di orip. Se la definizione della struttura dei dati viene modificata e nessuno pensa di cercare le inizializzazioni o non riesce a trovarle tutte o commette un errore durante la modifica, le cose andranno in pezzi.
Edward Falk,

3
La maggior parte (se non tutte) le strutture POSIX non hanno un ordine definito, ma solo membri definiti. (struct timeval){ .seconds = 0, .microseconds = 100 }sarà sempre di cento microsecondi, ma timeval { 0, 100 }potrebbe essere di cento SECONDI . Non vuoi trovare qualcosa del genere nel modo più duro.
yyny,

99

Dopo che la mia domanda non ha prodotto risultati soddisfacenti (poiché C ++ non implementa init basato su tag per strutture), ho preso il trucco che ho trovato qui: I membri di una struttura C ++ sono inizializzati su 0 per impostazione predefinita?

Per te equivarrebbe a farlo:

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

Questo è sicuramente il più vicino a quello che volevi originariamente (azzera tutti i campi tranne quelli che vuoi inizializzare).


10
Questo non funziona per oggetti staticamente non inizializzati
user877329

5
static address temp_address = {};funzionerà. Riempirlo in seguito dipende dal tempo di esecuzione, sì. È possibile ignorare questo fornendo una funzione statica che fa l'init per voi: static address temp_address = init_my_temp_address();.
Gui13,

In C ++ 11, init_my_temp_addresspuò essere una funzione lambda:static address temp_address = [] () { /* initialization code */ }();
dureuill

3
Cattiva idea, viola il principio RAII.
hxpax,

2
Davvero una cattiva idea: aggiungi un membro al tuo addresse non saprai mai di tutti i luoghi che creano addresse ora non inizializzano il tuo nuovo membro.
mystery_doctor


17

Gli identificatori di campo sono in effetti la sintassi dell'inizializzatore C. In C ++ basta dare i valori nell'ordine corretto senza i nomi dei campi. Sfortunatamente questo significa che devi dare loro tutti (in realtà puoi omettere i campi finali a zero e il risultato sarà lo stesso):

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

1
Sì, puoi sempre utilizzare l'inizializzazione della struttura allineata.
texasbruce,

3
Sì, attualmente sto usando solo questo metodo (inizializzazione di strutture allineate). Ma sento che la leggibilità non è buona. Dato che la mia struttura è grande, l'inizializzatore ha così tanti dati ed è difficile per me tracciare quale valore è assegnato a quale membro.
Dinesh PR,

7
@ DineshP.R. Quindi scrivi un costruttore!
Mr Lister,

2
@MrLister (o chiunque) Forse sono bloccato in una nuvola di stupidità in questo momento, ma ti interessa spiegare come un costruttore sarebbe molto meglio? Mi sembra che ci sia poca differenza tra fornire un gruppo di valori senza nome dipendenti dall'ordine a un elenco di inizializzatori o fornire un gruppo di valori senza nome dipendenti dall'ordine a un costruttore ...?
yano,

1
@yano Ad essere sincero, non ricordo davvero perché pensavo che un costruttore fosse la risposta al problema. Se ricordo, tornerò da te.
Lister,

13

Questa funzione è chiamata inizializzatori designati . È un'aggiunta allo standard C99. Tuttavia, questa funzionalità è stata lasciata fuori dal C ++ 11. Secondo The C ++ Programming Language, 4th edition, Section 44.3.3.2 (C Features non adottato da C ++):

Alcune aggiunte a C99 (rispetto a C89) non sono state deliberatamente adottate in C ++:

[1] Array a lunghezza variabile (VLA); usa il vettore o una qualche forma di matrice dinamica

[2] inizializzatori designati; usare i costruttori

La grammatica C99 ha gli inizializzatori designati [Vedi ISO / IEC 9899: 2011, N1570 Committee Draft - 12 aprile 2011]

6.7.9 Inizializzazione

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

D'altra parte, il C ++ 11 non ha gli inizializzatori designati [Vedi ISO / IEC 14882: 2011, N3690 Committee Draft - 15 maggio 2013]

8.5 Inizializzatori

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 }
    { }

Per ottenere lo stesso effetto, utilizzare i costruttori o gli elenchi di inizializzatori:


9

Puoi solo inizializzare tramite ctor:

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

9
Questo è il caso solo se controlli la definizione di struct address. Inoltre, i tipi di POD spesso non hanno intenzionalmente costruttore e distruttore.
user4815162342

7

Puoi persino comprimere la soluzione di Gui13 in una singola dichiarazione di inizializzazione:

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);

Disclaimer: non consiglio questo stile


Questo è ancora pericoloso perché ti consente di aggiungere un membro addresse il codice verrà comunque compilato con un milione di posti inizializzando solo i cinque membri originali. La parte migliore dell'inizializzazione di una struttura è che puoi avere tutti i membri conste ti costringerà a inizializzarli tutti
mystery_doctor

7

So che questa domanda è piuttosto vecchia, ma ho trovato un altro modo di inizializzare, usando constexpr e 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);

Questo metodo funziona anche per variabili statiche globali e anche per quelle di constexpr. L'unico svantaggio è la cattiva manutenibilità: ogni volta che un altro membro deve essere reso inizializzabile usando questo metodo, tutti i metodi di inizializzazione del membro devono essere cambiati.


1
Questo è il modello del costruttore . I metodi membro possono restituire un riferimento alla proprietà da modificare invece di creare una nuova struttura ogni volta
phuclv

6

Potrei mancare qualcosa qui, perché 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);
}

Ho compilato il programma sopra con un compilatore C ++ MinGW e un compilatore C ++ AVR C ++ ed entrambi hanno funzionato come previsto. Notare #include <cstdio>
run_the_race il

4
@run_the_race, si tratta di ciò che lo standard c ++ dice non quale potrebbe essere il comportamento di un determinato compilatore. Tuttavia, questa funzionalità è disponibile in c ++ 20.
Jon,

funziona solo se la struttura è POD. Quindi smetterà di compilare se aggiungi un costruttore ad esso.
oromoiluig,

5

Non è implementato in C ++. (anche, char*stringhe? Spero di no).

Di solito se hai così tanti parametri è un odore di codice abbastanza serio. Ma invece, perché non semplicemente inizializzare il valore della struttura e quindi assegnare ciascun membro?


6
"(anche, char*stringhe? Spero di no)." - Beh, è ​​un esempio C.
Ed S.

non possiamo usare char * in C ++? Attualmente lo sto usando e funziona (forse sto facendo qualcosa di sbagliato). La mia ipotesi è che il compilatore creerà stringhe costanti di "Hamilton" e "Ontario" e assegnerà il loro indirizzo ai membri della struttura. Sarà corretto usare const char * invece?
Dinesh PR,

8
Puoi usarlo char*ma const char*è molto più sicuro per il tipo e tutti lo usano std::stringperché è molto più affidabile.
Cucciolo

Ok. Quando ho letto "come menzionato di seguito" ho pensato che fosse un esempio copiato da qualche parte.
Ed S.

2

Ho trovato questo modo di farlo per le variabili globali, che non richiede di modificare la definizione della struttura originale:

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

quindi dichiarare la variabile di un nuovo tipo ereditata dal tipo di struttura originale e utilizzare il costruttore per l'inizializzazione dei campi:

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

Non abbastanza elegante come lo stile C però ...

Per una variabile locale richiede un memset aggiuntivo (this, 0, sizeof (* this)) all'inizio del costruttore, quindi chiaramente non è peggio e la risposta di @ gui13 è più appropriata.

(Si noti che "temp_address" è una variabile di tipo "temp_address", tuttavia questo nuovo tipo eredita da "address" e può essere utilizzato in ogni luogo in cui è previsto "address", quindi va bene.)


1

Ho affrontato un problema simile oggi, dove ho una struttura che voglio riempire di dati di test che verranno passati come argomenti a una funzione che sto testando. Volevo avere un vettore di queste strutture e stavo cercando un metodo one-liner per inizializzare ogni struttura.

Ho finito con una funzione di costruzione nella struttura, che credo sia stata suggerita anche in alcune risposte alla tua domanda.

Probabilmente è una cattiva pratica avere gli argomenti per il costruttore con gli stessi nomi delle variabili dei membri pubblici, che richiedono l'uso del thispuntatore. Qualcuno può suggerire una modifica se esiste un modo migliore.

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;

Che ho usato nella mia funzione di test per chiamare la funzione testata con vari argomenti come questo:

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

È possibile, ma solo se la struttura che stai inizializzando è una struttura POD (semplici vecchi dati). Non può contenere metodi, costruttori o valori predefiniti.


1

In C ++ gli inizializzatori in stile C sono stati sostituiti da costruttori che in fase di compilazione possono garantire che vengano eseguite solo inizializzazioni valide (cioè dopo l'inizializzazione i membri dell'oggetto sono coerenti).

È una buona pratica, ma a volte una pre-inizializzazione è utile, come nel tuo esempio. OOP risolve questo problema con classi astratte o modelli di progettazione creazionale .

A mio avviso, l'utilizzo di questo modo sicuro uccide la semplicità e talvolta il compromesso di sicurezza potrebbe essere troppo costoso, poiché un semplice codice non ha bisogno di un design sofisticato per rimanere sostenibile.

Come soluzione alternativa, suggerisco di definire le macro usando lambdas per semplificare l'inizializzazione in modo che assomigli quasi allo stile 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 ADDRESS si espande in

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

che crea e chiama la lambda. Anche i parametri macro sono separati da virgola, quindi è necessario mettere l'inizializzatore tra parentesi e chiamare like

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

È inoltre possibile scrivere un inizializzatore di macro generalizzato

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

ma poi la chiamata è leggermente meno bella

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

tuttavia è possibile definire facilmente la macro ADDRESS utilizzando la macro INIT generale

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


0

Ispirato da questa risposta davvero accurata: ( https://stackoverflow.com/a/49572324/4808079 )

Puoi fare chiusure di 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 $;
  }();
}

Oppure, se vuoi essere molto elegante

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

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

Ci sono alcuni inconvenienti coinvolti in questo, principalmente con membri non inizializzati. Da quello che dicono i commenti delle risposte collegate, si compila in modo efficiente, anche se non l'ho provato.

Nel complesso, penso solo che sia un approccio pulito.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.