Gdy definiujemy typ struktury (lub klasy), możemy podać domyślną wartość inicjującą dla każdego elementu członkowskiego w ramach definicji typu. W przypadku prętów, które nie są oznaczone jako static, proces ten jest czasami nazywany niestatyczną inicjalizacją elementu . Wartość inicjująca nazywa się a domyślnym inicjatorem elementu.
Powiązana treść
W lekcji omówimy elementy statyczne i inicjalizację pręta statycznego. 15.6 -- Statyczne zmienne składowe.
Oto przykład:
struct Something
{
int x; // no initialization value (bad)
int y {}; // value-initialized by default
int z { 2 }; // explicit default value
};
int main()
{
Something s1; // s1.x is uninitialized, s1.y is 0, and s1.z is 2
return 0;
}W powyższej definicji Something, x nie ma wartości domyślnej, y jest domyślnie inicjowana wartością i z ma wartość domyślną 2. Te domyślne wartości inicjalizacji elementu zostaną użyte, jeśli użytkownik nie poda jawnej wartości inicjalizacji podczas tworzenia instancji obiektu typu Something.
Nasz s1 obiekt nie ma inicjatora, więc elementy s1 są inicjalizowane do ich wartości domyślnych. s1.x nie ma domyślnego inicjatora, więc pozostaje niezainicjowany. s1.y jest wartością inicjowaną domyślnie, więc otrzymuje wartość 0. I s1.z jest inicjowany wartością 2.
Zauważ, że nawet jeśli nie udostępniliśmy jawnego inicjatora dla s1.z, jest on inicjowany na wartość różną od zera ze względu na dostarczony domyślny inicjator elementu członkowskiego.
Kluczowa informacja
Wykorzystując domyślne inicjatory składowe (lub inne mechanizmy, które omówimy później), struktury i klasy mogą samoinicjować się nawet wtedy, gdy nie są dostępne żadne jawne inicjatory zapewnione!
Dla zaawansowanych czytelników
CTAD (który omówimy w lekcji 13.14 -- Przewodniki dedukcji i dedukcji szablonu klasy (CTAD)) nie może być używany podczas niestatycznej inicjalizacji elementu członkowskiego.
Jawne wartości inicjujące mają pierwszeństwo przed wartościami domyślnymi
Jawne wartości w inicjatorze listy zawsze mają pierwszeństwo przed domyślnymi wartościami inicjalizacji elementu członkowskiego.
struct Something
{
int x; // no default initialization value (bad)
int y {}; // value-initialized by default
int z { 2 }; // explicit default value
};
int main()
{
Something s2 { 5, 6, 7 }; // use explicit initializers for s2.x, s2.y, and s2.z (no default values are used)
return 0;
}W powyższym przypadku s2 ma jawną inicjalizację wartości dla każdego elementu członkowskiego, więc domyślne wartości inicjowania elementu nie są w ogóle używane. Oznacza to, że s2.x, s2.y i s2.z są inicjalizowane wartościami 5, 6, I 7 .
Brakujące inicjatory na liście inicjatorów, gdy istnieją wartości domyślne
W poprzedniej lekcji (13.8 -- Inicjowanie agregatu Struct). Zauważyliśmy, że jeśli agregat zostanie zainicjowany, ale liczba wartości inicjujących jest mniejsza niż liczba elementów członkowskich, wówczas wszyscy pozostali członkowie zostaną zainicjowani wartościami. Jeśli jednak dla danego elementu podano domyślny inicjator elementu członkowskiego, zamiast niego zostanie użyty ten domyślny inicjator elementu.
struct Something
{
int x; // no default initialization value (bad)
int y {}; // value-initialized by default
int z { 2 }; // explicit default value
};
int main()
{
Something s3 {}; // value initialize s3.x, use default values for s3.y and s3.z
return 0;
}W powyższym przypadku s3 lista jest inicjalizowana pustą listą, więc brakuje wszystkich inicjatorów. Oznacza to, że zostanie użyty domyślny inicjator elementu członkowskiego, jeśli istnieje, a w przeciwnym razie nastąpi inicjalizacja wartości. Zatem s3.x (który nie ma domyślnego inicjatora składowego) jest wartością inicjowaną 0, s3.y jest wartością inicjowaną domyślnie na 0, I s3.z jest wartością domyślną 2.
Podsumowanie możliwości inicjalizacji
Jeśli agregat jest zdefiniowany za pomocą listy inicjalizacyjnej:
- Jeśli istnieje jawna wartość inicjalizacyjna, tą jawną wartością jest używany.
- Jeśli brakuje inicjatora, a istnieje domyślny inicjator elementu, używany jest domyślny.
- Jeśli brakuje inicjatora i nie istnieje domyślny inicjator elementu, następuje inicjalizacja wartości.
Jeśli zdefiniowano agregat bez listy inicjalizacyjnej:
- Jeśli istnieje domyślny inicjator elementu, używany jest domyślny.
- Jeśli nie istnieje domyślny inicjator elementu, członek pozostaje niezainicjowany.
Elementy są zawsze inicjowane w kolejności deklaracji.
Następujący przykład podsumowuje wszystkie możliwości:
struct Something
{
int x; // no default initialization value (bad)
int y {}; // value-initialized by default
int z { 2 }; // explicit default value
};
int main()
{
Something s1; // No initializer list: s1.x is uninitialized, s1.y and s1.z use defaults
Something s2 { 5, 6, 7 }; // Explicit initializers: s2.x, s2.y, and s2.z use explicit values (no default values are used)
Something s3 {}; // Missing initializers: s3.x is value initialized, s3.y and s3.z use defaults
return 0;
}Przypadek, na który chcemy zwrócić uwagę, to s1.x. Ponieważ s1 nie ma listy inicjatorów i x nie ma domyślnego inicjatora elementu, s1.x pozostaje niezainicjowany (co jest źle, ponieważ zawsze powinniśmy inicjować nasze zmienne).
Zawsze podawaj wartości domyślne dla swoich członków
Aby uniknąć możliwości wystąpienia niezainicjowanych elementów, po prostu upewnij się, że każdy element ma wartość domyślną (albo jawną wartość domyślną, albo pustą parę nawiasów klamrowych). W ten sposób nasi członkowie zostaną zainicjowani pewną wartością, niezależnie od tego, czy udostępnimy listę inicjatorów, czy nie.
Rozważmy następującą strukturę, która ma domyślne ustawienia dla wszystkich elementów:
struct Fraction
{
int numerator { }; // we should use { 0 } here, but for the sake of example we'll use value initialization instead
int denominator { 1 };
};
int main()
{
Fraction f1; // f1.numerator value initialized to 0, f1.denominator defaulted to 1
Fraction f2 {}; // f2.numerator value initialized to 0, f2.denominator defaulted to 1
Fraction f3 { 6 }; // f3.numerator initialized to 6, f3.denominator defaulted to 1
Fraction f4 { 5, 8 }; // f4.numerator initialized to 5, f4.denominator initialized to 8
return 0;
}We wszystkich przypadkach nasze elementy są inicjowane wartościami.
Najlepsza praktyka
Podaj wartość domyślną dla wszystkich elementów. Dzięki temu Twoje elementy zostaną zainicjowane, nawet jeśli definicja zmiennej nie zawiera listy inicjatorów.
Inicjalizacja domyślna a inicjalizacja wartości dla agregatów
Powracając do dwóch linii z powyższego przykładu:
Fraction f1; // f1.numerator value initialized to 0, f1.denominator defaulted to 1
Fraction f2 {}; // f2.numerator value initialized to 0, f2.denominator defaulted to 1Zauważysz, że f1 jest inicjowana domyślnie i f2 jest inicjalizowana wartością, ale wyniki są takie same (numerator jest inicjowany do 0 i denominator jest inicjowany do 1). Co więc powinniśmy preferować?
Przypadek inicjowania wartości (f2) jest bezpieczniejszy, ponieważ gwarantuje, że każdy element bez wartości domyślnych zostanie zainicjowany wartością (i chociaż zawsze powinniśmy podawać wartości domyślne dla elementów, chroni to przed przypadkiem, gdy któryś zostanie pominięty).
Preferowanie inicjowania wartości ma jeszcze jedną zaletę — jest spójne ze sposobem inicjowania obiektów innych typów. Spójność pomaga zapobiegać błędom.
Najlepsza praktyka
W przypadku agregatów preferuj inicjalizację wartości (z inicjatorem pustych nawiasów klamrowych) zamiast inicjalizacji domyślnej (bez nawiasów klamrowych).
To powiedziawszy, nierzadko programiści używają inicjalizacji domyślnej zamiast inicjalizacji wartości dla typów klas. Dzieje się tak częściowo ze względów historycznych (ponieważ inicjalizacja wartości została wprowadzona dopiero w C++ 11), a częściowo dlatego, że istnieje szczególny przypadek (w przypadku obiektów niebędących agregatami), w którym inicjalizacja domyślna może być bardziej wydajna niż inicjalizacja wartości (omawiamy ten przypadek w lekcji 14.11 -- Domyślne konstruktory i domyślne argumenty).
Dlatego nie będziemy bojowni o wymuszanie użycia inicjalizacji wartości dla struktur i klas w tych samouczkach, ale zdecydowanie to zalecamy.

