C ++ - Strukturinitialisierung

278

Ist es möglich, Strukturen in C ++ wie unten angegeben zu initialisieren?

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

In den Links hier und hier wird erwähnt, dass dieser Stil nur in C verwendet werden kann. Wenn ja, warum ist dies in C ++ nicht möglich? Gibt es einen technischen Grund, warum es nicht in C ++ implementiert ist, oder ist es eine schlechte Praxis, diesen Stil zu verwenden. Ich verwende diese Art der Initialisierung gerne, weil meine Struktur groß ist und dieser Stil mir eine klare Lesbarkeit darüber gibt, welcher Wert welchem ​​Mitglied zugewiesen ist.

Bitte teilen Sie mir mit, ob es andere Möglichkeiten gibt, mit denen wir die gleiche Lesbarkeit erreichen können.

Ich habe die folgenden Links verwiesen, bevor ich diese Frage gestellt habe

  1. C / C ++ für AIX
  2. C Strukturinitialisierung mit Variable
  3. Initialisierung der statischen Struktur mit Tags in C ++
  4. C ++ 11 Richtige Strukturinitialisierung
Dinesh PR
quelle
20
Persönliche Sicht auf die Welt: Sie benötigen diesen Stil der Objektinitialisierung in C ++ nicht, da Sie stattdessen einen Konstruktor verwenden sollten.
Philip Kendall
7
Ja, daran habe ich gedacht, aber ich habe eine Reihe großer Strukturen. Es wäre einfach und lesbar für mich, diesen Weg zu benutzen. Haben Sie einen Stil / eine gute Praxis für die Initialisierung mit Constructor, der auch eine bessere Lesbarkeit bietet?
Dinesh PR
2
Haben Sie die Boost-Parameterbibliothek in Kombination mit Konstruktoren in Betracht gezogen? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Tony Delroy
18
Nicht so programmierbezogen: Diese Adresse funktioniert nur in den USA. In Frankreich haben wir keine "Provinz", in anderen Teilen der Welt gibt es keine Postleitzahl, eine Großmutter eines Freundes lebt in einem so kleinen Dorf, dass ihre Adresse "Frau X, Postleitzahl" lautet kleiner Dorfname "(yep, keine Straße). Überlegen Sie also genau, auf welche gültige Adresse Sie den Markt anwenden werden;)
Matthieu M.
5
@MatthieuM. Es gibt keine Provinzen in den USA (dies könnte ein kanadisches Format sein?), Aber es gibt Staaten, Territorien und sogar winzige Dörfer, die sich nicht die Mühe machen, Straßen zu benennen. Das Problem der Adresskonformität gilt also auch hier.
Tim

Antworten:

165

Wenn Sie klarstellen möchten, was jeder Initialisierungswert ist, teilen Sie ihn einfach in mehrere Zeilen mit einem Kommentar zu jeder Zeile auf:

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};
Wyzard
quelle
7
Ich persönlich mag und empfehle diesen Stil
Dinesh PR
29
Was ist der Unterschied zwischen dem, was Sie tun, und der tatsächlichen Verwendung der Punktnotation, um GENAU auf das Feld selbst zuzugreifen? Es ist nicht so, als würden Sie Platz sparen, wenn dies das Problem ist. Ich verstehe C ++ - Programmierer wirklich nicht, wenn es darum geht, konsistent zu sein und wartbaren Code zu schreiben. Sie scheinen immer etwas anderes tun zu wollen, um ihren Code hervorzuheben. Der Code soll das Problem widerspiegeln, das gelöst wird und das es nicht sein sollte eine Redewendung für sich, die auf Zuverlässigkeit und Wartungsfreundlichkeit abzielt.
5
@ user1043000 Zum einen ist in diesem Fall die Reihenfolge, in der Sie Ihre Mitglieder platzieren, von größter Bedeutung. Wenn Sie ein Feld in der Mitte Ihrer Struktur hinzufügen, müssen Sie zu diesem Code zurückkehren und nach der genauen Stelle suchen, an der Sie Ihre neue Initialisierung einfügen können, was schwierig und langweilig ist. Mit der Punktnotation können Sie einfach Ihre neue Initialisierung am Ende der Liste einfügen, ohne sich um die Reihenfolge zu kümmern. Die Punktnotation ist viel sicherer, wenn Sie den gleichen Typ (wie char*) wie eines der anderen Elemente über oder unter der Struktur hinzufügen , da kein Risiko besteht, sie auszutauschen.
Gui13
6
orips Kommentar. Wenn die Datenstrukturdefinition geändert wird und niemand daran denkt, nach den Initialisierungen zu suchen, oder nicht alle finden kann oder einen Fehler beim Bearbeiten macht, fallen die Dinge auseinander.
Edward Falk
3
Die meisten (wenn nicht alle) POSIX-Strukturen haben keine definierte Reihenfolge, sondern nur definierte Mitglieder. (struct timeval){ .seconds = 0, .microseconds = 100 }wird immer hundert Mikrosekunden sein, timeval { 0, 100 }könnte aber hundert Sekunden sein . Sie wollen so etwas nicht auf die harte Tour herausfinden.
yyny
98

Nachdem meine Frage zu keinem zufriedenstellenden Ergebnis geführt hat (da C ++ kein tagbasiertes Init für Strukturen implementiert), habe ich den hier gefundenen Trick ausgeführt: Werden Mitglieder einer C ++ - Struktur standardmäßig auf 0 initialisiert?

Für Sie wäre es das:

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

Dies ist sicherlich das, was Sie ursprünglich am nächsten wollten (null aller Felder außer denen, die Sie initialisieren möchten).

Gui13
quelle
10
Dies funktioniert nicht für statisch initialisierte Objekte
user877329
5
static address temp_address = {};wird funktionieren. Das anschließende Auffüllen hängt von der Laufzeit ab, ja. Sie können dies umgehen, indem Sie eine statische Funktion bereitstellen, die den Init für Sie ausführt : static address temp_address = init_my_temp_address();.
Gui13
In C ++ 11 init_my_temp_addresskann eine Lambda-Funktion sein:static address temp_address = [] () { /* initialization code */ }();
Dureuill
3
Schlechte Idee, es verstößt gegen das RAII-Prinzip.
hxpax
2
Wirklich schlechte Idee: Wenn Sie ein Mitglied zu Ihrem hinzufügen, werden addressSie nie alle Orte kennen , an denen ein Mitglied erstellt wird, und addressIhr neues Mitglied jetzt nicht initialisieren.
Mystery_doctor
24

Wie andere bereits erwähnt haben, wird dies als Initialisierer bezeichnet.

Diese Funktion ist Teil von C ++ 20

Sameer Chaudhari
quelle
17

Die Feldbezeichner sind in der Tat C-Initialisierersyntax. In C ++ geben Sie einfach die Werte in der richtigen Reihenfolge ohne die Feldnamen an. Leider bedeutet dies, dass Sie alle angeben müssen (tatsächlich können Sie nachfolgende Felder mit dem Wert Null weglassen, und das Ergebnis ist das gleiche):

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 }; 
Gen
quelle
1
Ja, Sie können immer eine ausgerichtete Strukturinitialisierung verwenden.
Texasbruce
3
Ja, derzeit verwende ich nur diese Methode (Aligned Struct Initialization). Aber ich finde die Lesbarkeit nicht gut. Da meine Struktur groß ist, hat der Initialisierer so viele Daten und es ist schwierig für mich zu verfolgen, welcher Wert welchem ​​Mitglied zugewiesen ist.
Dinesh PR
7
@ DineshP.R. Dann schreibe einen Konstruktor!
Herr Lister
2
@ MrLister (oder irgendjemand) Vielleicht stecke ich im Moment in einer Wolke der Dummheit fest, möchte aber erklären, wie ein Konstruktor viel besser wäre? Mir scheint, es gibt kaum einen Unterschied zwischen der Bereitstellung einer Reihe von auftragsabhängigen unbenannten Werten für eine Initialisierungsliste oder der Bereitstellung einer Reihe von auftragsabhängigen unbenannten Werten für einen Konstruktor ...?
Yano
1
@yano Um ehrlich zu sein, ich erinnere mich nicht wirklich, warum ich dachte, ein Konstruktor wäre die Antwort auf das Problem. Wenn ich mich erinnere, komme ich zu dir zurück.
Herr Lister
13

Diese Funktion wird als designierte Initialisierer bezeichnet . Es ist eine Ergänzung zum C99-Standard. Diese Funktion wurde jedoch in C ++ 11 nicht berücksichtigt. Gemäß der Programmiersprache C ++, 4. Ausgabe, Abschnitt 44.3.3.2 (C-Funktionen, die von C ++ nicht übernommen wurden):

Einige Ergänzungen zu C99 (im Vergleich zu C89) wurden in C ++ bewusst nicht übernommen:

[1] Arrays variabler Länge (VLAs); Verwenden Sie einen Vektor oder eine Form eines dynamischen Arrays

[2] Ausgewiesene Initialisierer; Verwenden Sie Konstruktoren

Die C99-Grammatik hat die festgelegten Initialisierer [Siehe ISO / IEC 9899: 2011, N1570 Committee Draft - 12. April 2011]

6.7.9 Initialisierung

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

Andererseits verfügt das C ++ 11 nicht über die vorgesehenen Initialisierer [Siehe ISO / IEC 14882: 2011, N3690 Committee Draft - 15. Mai 2013]

8.5 Initialisierer

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

Verwenden Sie Konstruktoren oder Initialisiererlisten, um den gleichen Effekt zu erzielen:

Guilherme Ferreira
quelle
9

Sie können einfach über ctor initialisieren:

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};
nikc
quelle
9
Dies ist nur dann der Fall, wenn Sie die Definition von steuern struct address. Außerdem haben POD-Typen häufig absichtlich keinen Konstruktor und Destruktor.
user4815162342
7

Sie können die Gui13-Lösung sogar in eine einzelne Initialisierungsanweisung packen:

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

Haftungsausschluss: Ich empfehle diesen Stil nicht

user396672
quelle
Dies ist immer noch gefährlich, da Sie damit ein Mitglied hinzufügen können addressund der Code weiterhin mit einer Million Stellen kompiliert wird, wobei nur die ursprünglichen fünf Mitglieder initialisiert werden. Der beste Teil der Strukturinitialisierung ist, dass Sie alle Mitglieder haben können constund es Sie zwingen wird, sie alle zu initialisieren
secret_doctor
7

Ich weiß, dass diese Frage ziemlich alt ist, aber ich habe einen anderen Weg gefunden, sie mit constexpr und curry zu initialisieren:

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

Diese Methode funktioniert auch für globale statische Variablen und sogar für constexpr-Variablen. Der einzige Nachteil ist die schlechte Wartbarkeit: Jedes Mal, wenn ein anderes Mitglied mit dieser Methode initialisierbar gemacht werden muss, müssen alle Initialisierungsmethoden für Mitglieder geändert werden.

Fabian
quelle
1
Dies ist das Builder-Muster . Die Mitgliedsmethoden können einen Verweis auf die zu
ändernde
6

Ich könnte hier etwas vermissen, warum nicht:

#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);
}
run_the_race
quelle
Ich habe das obige Programm mit einem MinGW C ++ - Compiler und einem Arduino AVR C ++ - Compiler kompiliert und beide wurden wie erwartet ausgeführt. Beachten Sie die #include <cstdio>
run_the_race
4
@run_the_race, hier geht es darum, was der c ++ - Standard sagt, nicht um das Verhalten eines bestimmten Compilers. Diese Funktion ist jedoch in c ++ 20 verfügbar.
Jon
Dies funktioniert nur, wenn die Struktur POD ist. Das Kompilieren wird also beendet, wenn Sie einen Konstruktor hinzufügen.
Oromoiluig
5

Es ist nicht in C ++ implementiert. (ebenfalls,char* Saiten? Ich hoffe nicht).

Wenn Sie so viele Parameter haben, ist dies normalerweise ein ziemlich schwerwiegender Codegeruch. Aber warum nicht einfach die Struktur wertinitialisieren und dann jedem Mitglied zuweisen?

Hündchen
quelle
6
"(auch char*Saiten? Ich hoffe nicht)." - Nun, es ist ein C-Beispiel.
Ed S.
Können wir char * in C ++ nicht verwenden? Momentan benutze ich es und es funktioniert (möglicherweise mache ich etwas falsch). Ich gehe davon aus, dass der Compiler konstante Zeichenfolgen von "Hamilton" und "Ontario" erstellt und deren Adresse den Strukturmitgliedern zuweist. Ist es richtig, stattdessen const char * zu verwenden?
Dinesh PR
8
Sie können verwenden, char*aber es const char*ist viel typsicherer und jeder verwendet std::stringes nur, weil es viel zuverlässiger ist.
Welpe
OK. Als ich "wie unten erwähnt" las, nahm ich an, dass es sich um ein Beispiel handelte, das von irgendwoher kopiert wurde.
Ed S.
2

Ich habe diese Methode für globale Variablen gefunden, bei der die ursprüngliche Strukturdefinition nicht geändert werden muss:

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

Deklarieren Sie dann die Variable eines neuen Typs, der vom ursprünglichen Strukturtyp geerbt wurde, und verwenden Sie den Konstruktor für die Feldinitialisierung:

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

Nicht ganz so elegant wie der C-Stil ...

Für eine lokale Variable ist ein zusätzliches Memset (this, 0, sizeof (* this)) am Anfang des Konstruktors erforderlich, daher ist es eindeutig nicht schlechter und die Antwort von @ gui13 ist angemessener.

(Beachten Sie, dass 'temp_address' eine Variable vom Typ 'temp_address' ist. Dieser neue Typ erbt jedoch von 'address' und kann an jedem Ort verwendet werden, an dem 'address' erwartet wird. Daher ist dies in Ordnung.)

VincentP
quelle
1

Ich hatte heute ein ähnliches Problem, bei dem ich eine Struktur habe, die ich mit Testdaten füllen möchte, die als Argumente an eine Funktion übergeben werden, die ich teste. Ich wollte einen Vektor dieser Strukturen haben und suchte nach einer Einzeiler-Methode, um jede Struktur zu initialisieren.

Am Ende hatte ich eine Konstruktorfunktion in der Struktur, die meiner Meinung nach auch in einigen Antworten auf Ihre Frage vorgeschlagen wurde.

Es ist wahrscheinlich eine schlechte Praxis, wenn die Argumente an den Konstruktor dieselben Namen wie die öffentlichen Mitgliedsvariablen haben, was die Verwendung des thisZeigers erfordert . Jemand kann eine Bearbeitung vorschlagen, wenn es einen besseren Weg gibt.

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;

Was ich in meiner Testfunktion verwendet habe, um die zu testende Funktion mit verschiedenen Argumenten wie diesem aufzurufen:

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);
}
Unacoder
quelle
1

Es ist möglich, aber nur, wenn die Struktur, die Sie initialisieren, eine POD-Struktur (Plain Old Data) ist. Es darf keine Methoden, Konstruktoren oder sogar Standardwerte enthalten.

Wer weiß
quelle
1

In C ++ wurden die Initialisierer im C-Stil durch Konstruktoren ersetzt, die durch die Kompilierungszeit sicherstellen können, dass nur gültige Initialisierungen durchgeführt werden (dh nach der Initialisierung sind die Objektelemente konsistent).

Es ist eine gute Vorgehensweise, aber manchmal ist eine Vorinitialisierung praktisch, wie in Ihrem Beispiel. OOP löst dies durch abstrakte Klassen oder kreative Designmuster .

Meiner Meinung nach macht die Verwendung dieser sicheren Methode die Einfachheit zunichte und manchmal ist der Kompromiss zwischen Sicherheit zu teuer, da einfacher Code kein ausgeklügeltes Design benötigt, um wartbar zu bleiben.

Als alternative Lösung schlage ich vor, Makros mit Lambdas zu definieren, um die Initialisierung so zu vereinfachen, dass sie fast wie im C-Stil aussieht:

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

Das ADDRESS-Makro wird auf erweitert

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

das schafft und nennt das Lambda. Makroparameter werden ebenfalls durch Kommas getrennt, sodass Sie den Initialisierer in Klammern setzen und wie aufrufen müssen

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

Sie können auch einen verallgemeinerten Makroinitialisierer schreiben

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

aber dann ist der Anruf etwas weniger schön

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

Sie können das ADDRESS-Makro jedoch einfach mit dem allgemeinen INIT-Makro definieren

#define ADDRESS(x) INIT(address,x)
Jan Turoň
quelle
1

In GNUC ++ (scheint seit 2.5 vor langer Zeit veraltet zu sein :) Siehe die Antworten hier: C-Struktur-Initialisierung mit Labels. Es funktioniert, aber wie? ) ist es möglich, eine Struktur wie folgt zu initialisieren:

struct inventory_item {
    int bananas;
    int apples;
    int pineapples;
};

inventory_item first_item = {
    bananas: 2,
    apples: 49,
    pineapples: 4
};
Der Wissenschaftsjunge
quelle
0

Inspiriert von dieser wirklich tollen Antwort: ( https://stackoverflow.com/a/49572324/4808079 )

Sie können Lamba-Verschlüsse machen:

// 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 $;
  }();
}

Oder wenn Sie sehr schick sein wollen

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

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

Dies hat einige Nachteile, die hauptsächlich mit nicht initialisierten Mitgliedern zu tun haben. Nach den Kommentaren der verknüpften Antworten wird es effizient kompiliert, obwohl ich es nicht getestet habe.

Insgesamt denke ich nur, dass es ein ordentlicher Ansatz ist.

Seph Reed
quelle