Inicjalizacja struktury C ++

279

Czy można zainicjować struktury w C ++, jak wskazano poniżej

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

Linki tutaj i tutaj wspominają, że można używać tego stylu tylko w C. Jeśli tak, to dlaczego nie jest to możliwe w C ++? Czy istnieje jakiś podstawowy techniczny powód, dla którego nie jest on zaimplementowany w C ++, czy też jest to zła praktyka, aby używać tego stylu. Lubię używać tego sposobu inicjowania, ponieważ moja struktura jest duża, a ten styl daje mi wyraźną czytelność tego, jaka wartość jest przypisana do którego elementu.

Proszę podzielić się ze mną, jeśli istnieją inne sposoby, dzięki którym możemy osiągnąć taką samą czytelność.

Przed opublikowaniem tego pytania zapoznałem się z poniższymi linkami

  1. C / C ++ dla AIX
  2. C Inicjalizacja struktury za pomocą zmiennej
  3. Inicjalizacja struktury statycznej za pomocą znaczników w C ++
  4. C ++ 11 Właściwa inicjalizacja struktury
Dinesh PR
źródło
20
Osobiste spojrzenie na świat: nie potrzebujesz tego stylu inicjowania obiektów w C ++, ponieważ zamiast tego powinieneś używać konstruktora.
Philip Kendall,
7
Tak, myślałem o tym, ale mam szereg dużych struktur. Korzystanie z tego sposobu byłoby dla mnie łatwe i czytelne. Czy masz jakiś styl / dobrą praktykę inicjowania przy użyciu Konstruktora, która również zapewnia lepszą czytelność.
Dinesh PR
2
Czy rozważałeś bibliotekę parametrów doładowania w połączeniu z konstruktorami? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Tony Delroy
18
Nie dotyczy to programowania: ten adres działa dobrze tylko w Stanach Zjednoczonych. We Francji nie mamy „prowincji”, w innych częściach świata nie ma kodu pocztowego, babcia koleżanki mieszka w tak małej wiosce, że jej adres to „Pani X, kod pocztowy nazwa małej wioski "(tak, bez ulicy). Zastanów się więc, jaki jest prawidłowy adres na rynku, do którego będziesz go stosować;)
Matthieu M.,
5
@MatthieuM. W USA nie ma prowincji (może to być format kanadyjski?), Ale istnieją stany, terytoria, a nawet małe wioski, które nie zawracają sobie głowy nazwami ulic. Zatem kwestia zgodności adresu obowiązuje nawet tutaj.
Tim

Odpowiedzi:

166

Jeśli chcesz wyjaśnić, co to jest każda wartość inicjalizatora, po prostu podziel ją na wiele wierszy z komentarzem do każdego:

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};
Wyzard
źródło
7
Osobiście lubię i polecam ten styl
Dinesh PR
30
Jaka jest różnica między robieniem tego a faktycznym użyciem notacji kropkowej, aby uzyskać DOKŁADNY DOSTĘP do samego pola, to nie tak, że oszczędzasz miejsce, jeśli o to chodzi. Naprawdę nie dostaję programistów C ++, jeśli chodzi o spójność i pisanie łatwego do utrzymania kodu, wydaje się, że zawsze chcą zrobić coś innego, aby wyróżnić swój kod, kod ma odzwierciedlać rozwiązany problem, nie powinien być idiom sam w sobie, dążący do niezawodności i łatwości konserwacji.
5
@ user1043000 cóż, po pierwsze, w tym przypadku kolejność umieszczania członków ma ogromne znaczenie. Jeśli dodasz pole w środku swojej struktury, będziesz musiał wrócić do tego kodu i poszukać dokładnie miejsca, w którym chcesz wstawić nową inicjalizację, co jest trudne i nudne. Za pomocą notacji kropkowej możesz po prostu umieścić nową inicjalizację na końcu listy, nie zawracając sobie głowy kolejnością. Notacja kropkowa jest znacznie bezpieczniejsza, jeśli zdarzy się, że dodasz ten sam typ (jak char*) jak jeden z pozostałych elementów powyżej lub poniżej w strukturze, ponieważ nie ma ryzyka zamiany.
Gui13,
6
komentarz oripa. Jeśli definicja struktury danych ulegnie zmianie i nikt nie pomyśli o poszukiwaniu inicjalizacji, nie może znaleźć ich wszystkich lub nie popełni błędu podczas ich edytowania, wszystko się rozpadnie.
Edward Falk,
3
Większość (jeśli nie wszystkie) struktur POSIX nie ma określonej kolejności, tylko zdefiniowane elementy. (struct timeval){ .seconds = 0, .microseconds = 100 }będzie zawsze wynosić sto mikrosekund, ale timeval { 0, 100 }może wynosić sto SEKUND . Nie chcesz znaleźć czegoś takiego na własnej skórze.
yyny
99

Po tym, jak moje pytanie nie przyniosło zadowalającego rezultatu (ponieważ C ++ nie implementuje opartego na tagach init dla struktur), podjąłem sztuczkę, którą tu znalazłem: Czy członkowie struktury C ++ są domyślnie inicjowani na 0?

Dla ciebie oznaczałoby to zrobienie tego:

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

Z pewnością jest to najbliżej tego, co pierwotnie chciałeś (zeruj wszystkie pola oprócz tych, które chcesz zainicjować).

Gui13
źródło
10
To nie działa w przypadku obiektów statycznie niezainicjowanych
877329
5
static address temp_address = {};będzie działać. Tak, wypełnienie go później zależy od czasu wykonywania. Można ominąć ten poprzez dostarczanie funkcję statycznego, który nie init dla Ciebie: static address temp_address = init_my_temp_address();.
Gui13
W C ++ 11 init_my_temp_addressmoże być funkcją lambda:static address temp_address = [] () { /* initialization code */ }();
dureuill
3
Zły pomysł, narusza zasadę RAII.
hxpax
2
Naprawdę zły pomysł: dodaj jednego członka do swojego, addressa nigdy nie poznasz wszystkich miejsc, które go tworzą, addressa teraz nie inicjujesz nowego członka.
mystery_doctor
25

Jak wspomnieli inni, jest to inicjator.

Ta funkcja jest częścią C ++ 20

sameer chaudhari
źródło
17

Identyfikatory pól są rzeczywiście składnią inicjalizacyjną C. W C ++ podaj wartości we właściwej kolejności bez nazw pól. Niestety oznacza to, że musisz podać je wszystkie (w rzeczywistości możesz pominąć końcowe pola o zerowej wartości, a wynik będzie taki sam):

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 }; 
Gen
źródło
1
Tak, zawsze możesz użyć wyrównanej inicjalizacji struktury.
texasbruce
3
Tak, obecnie używam tylko tej metody (inicjalizacja wyrównywanych struktur). Ale uważam, że czytelność nie jest dobra. Ponieważ moja struktura jest duża, inicjator zawiera tak wiele danych i trudno jest mi wyśledzić, która wartość jest przypisana do którego elementu.
Dinesh PR
7
@ DineshP.R. Następnie napisz konstruktor!
Pan Lister,
2
@MrLister (lub ktokolwiek) Być może utknąłem w tej chwili w głupocie, ale czy chcesz wyjaśnić, w jaki sposób konstruktor byłby znacznie lepszy? Wydaje mi się, że nie ma różnicy między dostarczaniem wiązki nienazwanych wartości zależnych od kolejności do listy inicjalizującej a dostarczaniem wiązki nienazwanych wartości zależnych od kolejności do konstruktora ...?
yano
1
@yano Szczerze mówiąc, tak naprawdę nie pamiętam, dlaczego myślałem, że konstruktor będzie odpowiedzią na problem. Jeśli pamiętam, wrócę do ciebie.
Pan Lister,
13

Ta funkcja nazywa się wyznaczonymi inicjalizatorami . Jest dodatkiem do standardu C99. Jednak ta funkcja została pominięta w C ++ 11. Zgodnie z językiem programowania C ++, wydanie czwarte, sekcja 44.3.3.2 (Funkcje C niezaadaptowane przez C ++):

Kilka dodatków do C99 (w porównaniu z C89) celowo nie zostało przyjętych w C ++:

[1] Tablice o zmiennej długości (VLA); użyj wektora lub innej formy dynamicznej tablicy

[2] Wyznaczone inicjatory; użyj konstruktorów

Gramatyka C99 ma wyznaczone inicjatory [Patrz ISO / IEC 9899: 2011, Projekt komitetu N1570 - 12 kwietnia 2011 r.]

6.7.9 Inicjalizacja

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

Z drugiej strony C ++ 11 nie ma wyznaczonych inicjatorów [Patrz ISO / IEC 14882: 2011, N3690 Komitet Projekt - 15 maja 2013 r.]

8.5 Inicjatory

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

Aby osiągnąć ten sam efekt, użyj konstruktorów lub list inicjalizacyjnych:

Guilherme Ferreira
źródło
9

Możesz po prostu zainicjować za pomocą ctor:

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};
nikc
źródło
9
Dzieje się tak tylko wtedy, gdy kontrolujesz definicję struct address. Ponadto typy POD często celowo nie mają konstruktora i destruktora.
user4815162342
7

Możesz nawet spakować rozwiązanie Gui13 do pojedynczej instrukcji inicjalizacji:

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

Oświadczenie: Nie polecam tego stylu

użytkownik396672
źródło
Jest to nadal niebezpieczne, ponieważ pozwala na dodanie członka, addressa kod nadal będzie się kompilował z milionem miejsc inicjalizujących tylko pierwszych pięciu członków. Najlepszą częścią inicjalizacji struktury jest to, że możesz mieć wszystkich członków, consta to zmusi cię do zainicjowania ich wszystkich
mystery_doctor
7

Wiem, że to pytanie jest dość stare, ale znalazłem inny sposób inicjowania, używając constexpr i 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);

Ta metoda działa również w przypadku globalnych zmiennych statycznych, a nawet zmiennych constexpr. Jedyną wadą jest zła konserwowalność: za każdym razem, gdy trzeba zainicjować innego członka za pomocą tej metody, wszystkie metody inicjowania członka muszą zostać zmienione.

fabiański
źródło
1
To jest wzorzec konstruktora . Metody
składowe
6

Być może coś mi tu brakuje, dlaczego nie:

#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
źródło
Skompilowałem powyższy program przy użyciu kompilatora MinGW C ++ i kompilatora Arduino AVR C ++ i oba działały zgodnie z oczekiwaniami. Zwróć uwagę na #include <cstdio>
run_the_race
4
@run_the_race, chodzi o to, co standard c ++ nie mówi, jakie może być zachowanie danego kompilatora. Ta funkcja jest jednak dostępna w wersji c ++ 20.
Jon
działa to tylko wtedy, gdy struct jest POD. Więc przestanie się kompilować, jeśli dodasz do niego konstruktor.
oromoiluig
5

Nie jest zaimplementowany w C ++. (także char*ciągi? Mam nadzieję, że nie).

Zwykle, jeśli masz tak wiele parametrów, jest to dość poważny zapach kodu. Ale zamiast tego, dlaczego nie zainicjować wartości struktury, a następnie przypisać każdego członka?

Szczeniak
źródło
6
„(także char*łańcuchy? Mam nadzieję, że nie).” - Cóż, to jest przykład C.
Ed S.
nie możemy używać char * w C ++? Obecnie go używam i działa (być może robię coś złego). Zakładam, że kompilator utworzy ciągi znaków „Hamilton” i „Ontario” i przypisze swój adres członkom struktury. Czy zamiast tego poprawne będzie użycie const char *?
Dinesh PR
8
Możesz używać, char*ale const char*jest o wiele bardziej bezpieczny dla typu i wszyscy po prostu używają, std::stringponieważ jest o wiele bardziej niezawodny.
Szczeniak
Dobrze. Kiedy przeczytałem „jak wspomniano poniżej”, założyłem, że to skądś skopiowany przykład.
Ed S.,
2

Znalazłem ten sposób na zrobienie tego dla zmiennych globalnych, które nie wymagają modyfikacji oryginalnej definicji struktury:

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

następnie zadeklaruj zmienną nowego typu odziedziczoną z oryginalnego typu struktury i użyj konstruktora do inicjalizacji pól:

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

Jednak nie tak elegancki jak styl C ...

W przypadku zmiennej lokalnej wymaga ona dodatkowej pamięci (this, 0, sizeof (* this)) na początku konstruktora, więc wyraźnie nie jest gorzej, a odpowiedź @ gui13 jest bardziej odpowiednia.

(Zauważ, że „temp_address” jest zmienną typu „temp_address”, jednak ten nowy typ dziedziczy po „adres” i może być używany w każdym miejscu, w którym oczekiwany jest „adres”, więc jest OK.)

VincentP
źródło
1

Napotkałem dzisiaj podobny problem, w którym mam strukturę, którą chcę wypełnić danymi testowymi, które zostaną przekazane jako argumenty funkcji, którą testuję. Chciałem mieć wektor tych struktur i szukałem metody jednowierszowej do inicjalizacji każdej struktury.

Skończyłem z funkcją konstruktora w strukturze, która, jak sądzę, została również zasugerowana w kilku odpowiedziach na twoje pytanie.

Prawdopodobnie złą praktyką jest, aby argumenty konstruktora miały takie same nazwy jak publiczne zmienne składowe, co wymaga użycia thiswskaźnika. Ktoś może zaproponować edycję, jeśli istnieje lepszy sposób.

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;

Którego użyłem w mojej funkcji testowej do wywołania testowanej funkcji z różnymi argumentami takimi jak ten:

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
źródło
1

Jest to możliwe, ale tylko wtedy, gdy inicjowana struktura jest strukturą POD (zwykłe stare dane). Nie może zawierać żadnych metod, konstruktorów ani nawet wartości domyślnych.

kto wie
źródło
1

W C ++ inicjatory w stylu C zostały zastąpione konstruktorami, które przez czas kompilacji mogą zapewnić, że wykonywane są tylko prawidłowe inicjalizacje (tj. Po inicjalizacji elementy obiektu są spójne).

Jest to dobra praktyka, ale czasami przydatna jest wstępna inicjalizacja, jak w twoim przykładzie. OOP rozwiązuje to za pomocą klas abstrakcyjnych lub wzorców projektowych .

Moim zdaniem użycie tego bezpiecznego sposobu zabija prostotę, a czasem kompromis w zakresie bezpieczeństwa może być zbyt kosztowny, ponieważ prosty kod nie wymaga skomplikowanego projektu, aby był łatwy w utrzymaniu.

Jako alternatywne rozwiązanie sugeruję zdefiniowanie makr za pomocą lambd, aby uprościć inicjalizację, aby wyglądała prawie jak w stylu 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

Makro ADRES rozwija się do

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

który tworzy i nazywa lambda. Makroparametry są również oddzielone przecinkami, więc musisz umieścić inicjator w nawiasach i wywołać jak

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

Możesz także napisać ogólny inicjalizator makr

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

ale wtedy połączenie jest nieco mniej piękne

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

można jednak łatwo zdefiniować makro ADRES przy użyciu ogólnego makra INIT

#define ADDRESS(x) INIT(address,x)
Jan Turoň
źródło
1

W GNUC ++ (wydaje się być przestarzały od 2.5, dawno temu :) Zobacz odpowiedzi tutaj: Inicjalizacja struktury C za pomocą etykiet. Działa, ale jak? ), możliwe jest zainicjowanie struktury takiej jak ta:

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

inventory_item first_item = {
    bananas: 2,
    apples: 49,
    pineapples: 4
};
The Science Boy
źródło
0

Zainspirowany tą naprawdę zgrabną odpowiedzią: ( https://stackoverflow.com/a/49572324/4808079 )

Możesz zrobić zamknięcia 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 $;
  }();
}

Lub, jeśli chcesz być bardzo fantazyjny

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

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

Wiąże się to z pewnymi wadami, głównie związanymi z niezainicjowanymi członkami. Z tego, co mówią komentarze w odpowiedziach połączonych, kompiluje się wydajnie, chociaż go nie testowałem.

Ogólnie uważam, że to dobre podejście.

Seph Reed
źródło