Jestem bardzo zdezorientowany co do inicjalizacji wartości i domyślnej oraz zerowej. a zwłaszcza wtedy, gdy zaczynają stosować różne standardy C ++ 03 i C ++ 11 (i C ++ 14 ).
Cytuję i próbuję rozszerzyć naprawdę dobrą odpowiedź Value- / Default- / Zero- Init C ++ 98 i C ++ 03 tutaj, aby uczynić ją bardziej ogólną, ponieważ pomogłoby to wielu użytkownikom, gdyby ktoś pomógł wypełnić potrzebne luki, aby mieć dobry przegląd tego, co się dzieje, kiedy?
Pełen wgląd w przykłady w pigułce:
Czasami pamięć zwrócona przez nowego operatora zostanie zainicjowana, a czasami nie będzie to zależało od tego, czy typ, który tworzysz, to POD (zwykłe stare dane) , czy też jest to klasa, która zawiera członków POD i używa Konstruktor domyślny generowany przez kompilator.
- W C ++ 1998 istnieją 2 typy inicjalizacji: inicjalizacja zerowa i inicjalizacja domyślna
- W C ++ 2003 dodano trzeci typ inicjalizacji: inicjalizacja wartości .
- W C ++ 2011 / C ++ 2014 dodano tylko inicjalizację listy, a zasady inicjalizacji wartości / domyślnej / zerowej nieco się zmieniły.
Założyć:
struct A { int m; };
struct B { ~B(); int m; };
struct C { C() : m(){}; ~C(); int m; };
struct D { D(){}; int m; };
struct E { E() = default; int m;}; /** only possible in c++11/14 */
struct F {F(); int m;}; F::F() = default; /** only possible in c++11/14 */
W kompilatorze C ++ 98 powinno wystąpić :
new A
- wartość nieokreślona (A
to POD)new A()
- inicjalizacja zerowanew B
- domyślna konstrukcja (nieB::m
jest zainicjowana,B
nie jest POD)new B()
- domyślna konstrukcja (B::m
jest niezainicjowana)new C
- domyślna konstrukcja (C::m
jest zainicjowana przez zero,C
nie jest POD)new C()
- domyślna konstrukcja (C::m
jest inicjalizowana przez zero)new D
- domyślna konstrukcja (nieD::m
jest zainicjowana,D
nie jest POD)new D()
- domyślna konstrukcja? (nieD::m
jest zainicjowany)
W kompilatorze zgodnym z C ++ 03 wszystko powinno wyglądać tak:
new A
- wartość nieokreślona (A
to POD)new A()
- inicjalizacja wartościA
, która jest inicjalizacją zerową, ponieważ jest to POD.new B
- default-inicjalizuje (pozostawiaB::m
niezainicjowany,B
nie jest POD)new B()
- wartość-inicjalizuje,B
która inicjalizuje wszystkie pola zerem, ponieważ jego domyślny procesor jest generowany przez kompilator, a nie zdefiniowany przez użytkownika.new C
- default-initializesC
, co wywołuje domyślny ctor. (C::m
jest zainicjowany przez zero,C
nie jest POD)new C()
- inicjalizuje wartośćC
, która wywołuje domyślny ctor. (C::m
jest zainicjowany przez zero)new D
- domyślna konstrukcja (nieD::m
jest zainicjowana,D
nie jest POD)new D()
- wartość-inicjalizuje D? , który wywołuje domyślnego ctora (D::m
jest niezainicjowany)
Wartości kursywą i? są niejasne, pomóż to poprawić :-)
W kompilatorze zgodnym z C ++ 11 wszystko powinno wyglądać tak:
??? (proszę o pomoc, jeśli zacznę tutaj, to i tak pójdzie źle)
W kompilatorze zgodnym z C ++ 14 wszystko powinno wyglądać tak: ??? (proszę o pomoc, jeśli zacznę tutaj, to i tak pójdzie źle) (Wersja robocza oparta na odpowiedzi)
new A
- default-inicjalizujeA
, kompilator gen. ctor, (odchodziA::m
niezainicjalizowany) (A
jest POD)new A()
- inicjalizacja wartościA
, która jest inicjalizacją zerową od 2. punktu w [dcl.init] / 8new B
- default-inicjalizujeB
, kompilator gen. ctor, (opuszczaB::m
niezainicjalizowany) (B
nie jest POD)new B()
- wartość-inicjalizuje,B
która inicjalizuje wszystkie pola zerem, ponieważ jego domyślny procesor jest generowany przez kompilator, a nie zdefiniowany przez użytkownika.new C
- default-initializesC
, co wywołuje domyślny ctor. (C::m
jest zainicjowany przez zero,C
nie jest POD)new C()
- inicjalizuje wartośćC
, która wywołuje domyślny ctor. (C::m
jest zainicjowany przez zero)new D
- default-inicjalizujeD
(nieD::m
jest zainicjowany ,D
nie jest POD)new D()
- inicjalizuje wartośćD
, która wywołuje domyślny ctor (D::m
jest niezainicjowany)new E
- default-inicjalizujeE
, co wywołuje komp. gen. ctor. (nieE::m
jest zainicjowany, E nie jest POD)new E()
- inicjalizuje wartośćE
, która inicjalizuje zeroE
od 2 punktu w [dcl.init] / 8 )new F
- default-inicjalizujeF
, co wywołuje komp. gen. ctor. (nieF::m
jest zainicjowany,F
nie jest POD)new F()
- inicjalizuje wartośćF
, która inicjalizuje się domyślnieF
od 1. punktu w [dcl.init] / 8 (F
funkcja ctor jest dostarczana przez użytkownika, jeśli została zadeklarowana przez użytkownika i nie została jawnie ustawiona jako domyślna lub usunięta przy pierwszej deklaracji. Link )
struct D { D() {}; int m; };
może warto umieścić je na liście.Odpowiedzi:
C ++ 14 określa inicjalizację obiektów utworzonych za pomocą
new
w [wyr.new] / 17 ([wyr.new] / 15 w C ++ 11, a notatka nie była wówczas notatką, ale tekstem normatywnym):Inicjalizacja domyślna jest zdefiniowana w [dcl.init] / 7 (/ 6 w C ++ 11, a samo sformułowanie ma ten sam efekt):
A zatem
new A
powoduje jedynieA
wywołanie domyślnego konstruktora, który nie jest inicjowanym
. Wartość nieokreślona. Powinien być taki sam dlanew B
.new A()
jest interpretowany zgodnie z [dcl.init] / 11 (/ 10 w C ++ 11):A teraz rozważ [dcl.init] / 8 (/ 7 w C ++ 11 †):
Stąd
new A()
inicjalizacja zerowam
. Powinno to być równoważne dlaA
iB
.new C
inew C()
ponownie zainicjuje obiekt domyślnie, ponieważ obowiązuje pierwszy punkt z ostatniego cudzysłowu (C ma domyślny konstruktor dostarczony przez użytkownika!). Ale najwyraźniejm
w obu przypadkach now jest inicjowany w konstruktorze.† Cóż, ten akapit ma nieco inne brzmienie w C ++ 11, co nie zmienia wyniku:
źródło
struct A { int m; }; struct C { C() : m(){}; int m; };
generują różne wyniki i co powoduje, że m in A jest inicjowany w pierwszej kolejności. Otworzyłem dedykowany wątek dotyczący eksperymentu, który przeprowadziłem i będę wdzięczny za Twój wkład w wyjaśnienie problemu. Dzięki stackoverflow.com/questions/45290121/…Poniższa odpowiedź rozszerza odpowiedź https://stackoverflow.com/a/620402/977038, która służyłaby jako odniesienie dla C ++ 98 i C ++ 03
Cytując odpowiedź
C ++ 11 (w odniesieniu do n3242)
Inicjatory
8.5 Inicjatory [dcl.init] określa, że zmienny POD lub inny element POD może być zainicjowany jako inicjator nawiasów klamrowych lub równy, który może być listą inicjalizacyjną lub klauzulą inicjalizatora, zbiorczo określaną jako nawias klamrowy lub równy inicjator lub użycie (lista-wyrażeń) . Przed C ++ 11 obsługiwane były tylko (lista-wyrażeń) lub klauzula inicjująca, chociaż klauzula inicjująca była bardziej ograniczona niż to, co mamy w C ++ 11. W C ++ 11 klauzula inicjalizacyjna obsługuje teraz listę braced-init- oprócz wyrażenia przypisaniatak jak w C ++ 03. Poniższa gramatyka podsumowuje nową obsługiwaną klauzulę, w której część jest pogrubiona i została nowo dodana w standardzie C ++ 11.
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}
{}
Inicjalizacja
Podobnie jak C ++ 03, C ++ 11 nadal obsługuje trzy formy inicjalizacji
Uwaga
Typ inicjalizatora: 8.5.5 [dcl.init] _zero-initialize_
Wykonano w następujących przypadkach
2. Typ inicjalizatora: 8.5.6 [dcl.init] _default-initialize_
Wykonano w następujących przypadkach
3. Typ inicjalizatora: 8.5.7 [dcl.init] _value-initialize_
Podsumowując
źródło
Mogę potwierdzić, że w C ++ 11 wszystko wymienione w pytaniu pod C ++ 14 jest poprawne, przynajmniej zgodnie z implementacjami kompilatora.
Aby to sprawdzić, dodałem następujący kod do mojego zestawu testów . Testowałem
-std=c++11 -O3
w GCC 7.4.0, GCC 5.4.0, Clang 10.0.1 i VS 2017, a wszystkie poniższe testy przeszły pomyślnie.#include <gtest/gtest.h> #include <memory> struct A { int m; }; struct B { int m; ~B(){}; }; struct C { int m; C():m(){}; ~C(){}; }; struct D { int m; D(){}; }; struct E { int m; E() = default; }; struct F { int m; F(); }; F::F() = default; // We use this macro to fill stack memory with something else than 0. // Subsequent calls to EXPECT_NE(a.m, 0) are undefined behavior in theory, but // pass in practice, and help illustrate that `a.m` is indeed not initialized // to zero. Note that we initially tried the more aggressive test // EXPECT_EQ(a.m, 42), but it didn't pass on all compilers (a.m wasn't equal to // 42, but was still equal to some garbage value, not zero). // #define FILL { int m = 42; EXPECT_EQ(m, 42); } // We use this macro to fill heap memory with something else than 0, before // doing a placement new at that same exact location. Subsequent calls to // EXPECT_EQ(a->m, 42) are undefined behavior in theory, but pass in practice, // and help illustrate that `a->m` is indeed not initialized to zero. // #define FILLH(b) std::unique_ptr<int> bp(new int(42)); int* b = bp.get(); EXPECT_EQ(*b, 42) TEST(TestZero, StackDefaultInitialization) { { FILL; A a; EXPECT_NE(a.m, 0); } // UB! { FILL; B a; EXPECT_NE(a.m, 0); } // UB! { FILL; C a; EXPECT_EQ(a.m, 0); } { FILL; D a; EXPECT_NE(a.m, 0); } // UB! { FILL; E a; EXPECT_NE(a.m, 0); } // UB! { FILL; F a; EXPECT_NE(a.m, 0); } // UB! } TEST(TestZero, StackValueInitialization) { { FILL; A a = A(); EXPECT_EQ(a.m, 0); } { FILL; B a = B(); EXPECT_EQ(a.m, 0); } { FILL; C a = C(); EXPECT_EQ(a.m, 0); } { FILL; D a = D(); EXPECT_NE(a.m, 0); } // UB! { FILL; E a = E(); EXPECT_EQ(a.m, 0); } { FILL; F a = F(); EXPECT_NE(a.m, 0); } // UB! } TEST(TestZero, StackListInitialization) { { FILL; A a{}; EXPECT_EQ(a.m, 0); } { FILL; B a{}; EXPECT_EQ(a.m, 0); } { FILL; C a{}; EXPECT_EQ(a.m, 0); } { FILL; D a{}; EXPECT_NE(a.m, 0); } // UB! { FILL; E a{}; EXPECT_EQ(a.m, 0); } { FILL; F a{}; EXPECT_NE(a.m, 0); } // UB! } TEST(TestZero, HeapDefaultInitialization) { { FILLH(b); A* a = new (b) A; EXPECT_EQ(a->m, 42); } // ~UB { FILLH(b); B* a = new (b) B; EXPECT_EQ(a->m, 42); } // ~UB { FILLH(b); C* a = new (b) C; EXPECT_EQ(a->m, 0); } { FILLH(b); D* a = new (b) D; EXPECT_EQ(a->m, 42); } // ~UB { FILLH(b); E* a = new (b) E; EXPECT_EQ(a->m, 42); } // ~UB { FILLH(b); F* a = new (b) F; EXPECT_EQ(a->m, 42); } // ~UB } TEST(TestZero, HeapValueInitialization) { { FILLH(b); A* a = new (b) A(); EXPECT_EQ(a->m, 0); } { FILLH(b); B* a = new (b) B(); EXPECT_EQ(a->m, 0); } { FILLH(b); C* a = new (b) C(); EXPECT_EQ(a->m, 0); } { FILLH(b); D* a = new (b) D(); EXPECT_EQ(a->m, 42); } // ~UB { FILLH(b); E* a = new (b) E(); EXPECT_EQ(a->m, 0); } { FILLH(b); F* a = new (b) F(); EXPECT_EQ(a->m, 42); } // ~UB } TEST(TestZero, HeapListInitialization) { { FILLH(b); A* a = new (b) A{}; EXPECT_EQ(a->m, 0); } { FILLH(b); B* a = new (b) B{}; EXPECT_EQ(a->m, 0); } { FILLH(b); C* a = new (b) C{}; EXPECT_EQ(a->m, 0); } { FILLH(b); D* a = new (b) D{}; EXPECT_EQ(a->m, 42); } // ~UB { FILLH(b); E* a = new (b) E{}; EXPECT_EQ(a->m, 0); } { FILLH(b); F* a = new (b) F{}; EXPECT_EQ(a->m, 42); } // ~UB } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
Miejsca, w których
UB!
jest mowa, to niezdefiniowane zachowanie, a rzeczywiste zachowanie prawdopodobnie zależy od wielu czynników (a.m
może być równe 42, 0 lub innym śmieciom). Miejsca, o których~UB
wspomina się, są również nieokreślonymi zachowaniami w teorii, ale w praktyce, ze względu na użycie nowego miejsca docelowego, jest bardzo mało prawdopodobne,a->m
że będzie równe czegokolwiek innemu niż 42.źródło