Je suis très confus au sujet de l'initialisation de la valeur et par défaut et zéro. et surtout lorsqu'ils se lancent dans les différents standards C ++ 03 et C ++ 11 (et C ++ 14 ).
Je cite et j'essaie d'étendre une très bonne réponse Value- / Default- / Zero- Init C ++ 98 et C ++ 03 ici pour la rendre plus générale car cela aiderait beaucoup d'utilisateurs si quelqu'un pouvait aider à remplir le besoin de lacunes pour avoir une bonne vue d'ensemble de ce qui se passe quand?
Un aperçu complet par des exemples en un mot:
Parfois, la mémoire retournée par l'opérateur new sera initialisée, et parfois cela ne le sera pas selon que le type que vous créez est un POD (données anciennes simples) , ou s'il s'agit d'une classe qui contient des membres POD et utilise un constructeur par défaut généré par le compilateur.
- Dans C ++ 1998, il existe 2 types d'initialisation: zéro et initialisation par défaut
- En C ++ 2003, un troisième type d'initialisation, l'initialisation de la valeur a été ajoutée.
- Dans C ++ 2011 / C ++ 2014, seule l' initialisation de liste a été ajoutée et les règles d' initialisation de valeur / par défaut / zéro ont un peu changé.
Présumer:
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 */
Dans un compilateur C ++ 98, ce qui suit doit se produire :
new A
- valeur indéterminée (A
est POD)new A()
- initialisation à zéronew B
- construction par défaut (B::m
non initialisée,B
non-POD)new B()
- construction par défaut (B::m
non initialisée)new C
- construction par défaut (C::m
initialisée à zéro,C
non POD)new C()
- construction par défaut (C::m
est initialisé à zéro)new D
- construction par défaut (D::m
non initialisée,D
non-POD)new D()
- construction par défaut? (D::m
n'est pas initialisé)
Dans un compilateur conforme C ++ 03, les choses devraient fonctionner comme ceci:
new A
- valeur indéterminée (A
est POD)new A()
- value-initializeA
, qui est une initialisation à zéro puisqu'il s'agit d'un POD.new B
- initialise par défaut (laisseB::m
non initialisé,B
n'est pas POD)new B()
- value-initializesB
qui initialise à zéro tous les champs puisque son ctor par défaut est généré par le compilateur et non défini par l'utilisateur.new C
- default-initializesC
, qui appelle le ctor par défaut. (C::m
est initialisé à zéro,C
est non POD)new C()
- value-initializesC
, qui appelle le ctor par défaut. (C::m
est initialisé à zéro)new D
- construction par défaut (D::m
non initialisée,D
non POD)new D()
- valeur-initialise D? , qui appelle le ctor par défaut (D::m
n'est pas initialisé)
Valeurs italiques et? sont des incertitudes, veuillez aider à corriger cela :-)
Dans un compilateur conforme C ++ 11, les choses devraient fonctionner comme ceci:
??? (aidez-moi si je commence ici, ça ira de toute façon mal)
Dans un compilateur conforme C ++ 14, les choses devraient fonctionner comme suit: ??? (Aidez-moi si je commence ici, ça ira de toute façon mal) (Brouillon basé sur la réponse)
new A
- initialise par défautA
, compilateur gen. ctor, (laisseA::m
non initialisé) (A
est POD)new A()
- value-initializesA
, qui est une initialisation à zéro depuis 2. point dans [dcl.init] / 8new B
- initialise par défautB
, compilateur gen. ctor, (laisseB::m
non initialisé) (B
est non-POD)new B()
- value-initializesB
qui initialise à zéro tous les champs puisque son ctor par défaut est généré par le compilateur et non défini par l'utilisateur.new C
- default-initializesC
, qui appelle le ctor par défaut. (C::m
est initialisé à zéro,C
est non POD)new C()
- value-initializesC
, qui appelle le ctor par défaut. (C::m
est initialisé à zéro)new D
- initialise par défautD
(D::m
n'est pas initialisé,D
n'est pas POD)new D()
- value-initializesD
, qui appelle le ctor par défaut (D::m
n'est pas initialisé)new E
- default-initializesE
, qui appelle le comp. gen. ctor. (E::m
n'est pas initialisé, E est non-POD)new E()
- value-initializesE
, qui s'initialise à zéroE
depuis 2 points dans [dcl.init] / 8 )new F
- default-initializesF
, qui appelle le comp. gen. ctor. (F::m
n'est pas initialisé,F
n'est pas POD)new F()
- value-initializesF
, qui s'initialise par défautF
depuis 1. point dans [dcl.init] / 8 (laF
fonction ctor est fournie par l'utilisateur si elle est déclarée par l'utilisateur et non explicitement par défaut ou supprimée lors de sa première déclaration. Lien )
struct D { D() {}; int m; };
peut valoir la peine d'être inclus dans votre liste.Réponses:
C ++ 14 spécifie l'initialisation des objets créés avec
new
dans [expr.new] / 17 ([expr.new] / 15 dans C ++ 11, et la note n'était pas une note mais un texte normatif à l'époque):L'initialisation par défaut est définie dans [dcl.init] / 7 (/ 6 en C ++ 11, et le libellé lui-même a le même effet):
Donc
new A
provoque uniquement l'A
appel du constructeur par défaut, qui ne s'initialise pasm
. Valeur indéterminée. Devrait être la même chose pournew B
.new A()
est interprété selon [dcl.init] / 11 (/ 10 en C ++ 11):Et maintenant, considérez [dcl.init] / 8 (/ 7 en C ++ 11 †):
Par conséquent
new A()
, zéro-initialisationm
. Et cela devrait être équivalent pourA
etB
.new C
etnew C()
réinitialisera l'objet par défaut, puisque la première puce du dernier devis s'applique (C a un constructeur par défaut fourni par l'utilisateur!). Mais, clairement, maintenantm
est initialisé dans le constructeur dans les deux cas.† Eh bien, ce paragraphe a un libellé légèrement différent en C ++ 11, ce qui ne modifie pas le résultat:
la source
struct A { int m; }; struct C { C() : m(){}; int m; };
produire des résultats différents et ce qui provoque l'initialisation de m dans A en premier lieu. J'ai ouvert un fil dédié à l'expérience que j'ai faite et j'apprécierai votre contribution pour clarifier le problème. Merci stackoverflow.com/questions/45290121/…La réponse suivante étend la réponse https://stackoverflow.com/a/620402/977038 qui servirait de référence pour C ++ 98 et C ++ 03
Citant la réponse
C ++ 11 (en référence à n3242)
Initialiseurs
8.5 Initializers [dcl.init] spécifie qu'une variable POD ou non POD peut être initialisée soit en tant que brace-or-equal-initializer qui peut être braced-init-list ou initializer-clause agrégée comme accolade-or-equal- initialiseur ou en utilisant (liste d'expressions) . Avant C ++ 11, seule la (liste d'expressions) ou la clause d'initialisation était prise en charge bien que la clause d'initialisation soit plus restreinte que ce que nous avons dans C ++ 11. Dans C ++ 11, la clause d'initialisation prend désormais en charge braced-init-list en dehors de l' expression d' affectationcomme c'était le cas en C ++ 03. La grammaire suivante résume la nouvelle clause prise en charge, où la partie est en gras est nouvellement ajoutée dans la norme C ++ 11.
initialiseur: initialiseur d'
accolade ou d'égalité
(liste d'expressions) initialiseur d'
accolade ou d'égalité:
= clause d' initialisation
liste d'
initialisation accolée clause d'initialisation:
expression d'affectation
liste d'
initialisation accolée liste d' initialisation:
clause d'initialisation ... opt
initializer-list, initializer-clause ... opt **
braced-init-list:
{initializer-list, opt}
{}
Initialisation
Comme C ++ 03, C ++ 11 prend toujours en charge trois formes d'initialisation
Remarque
Type d'initialiseur: 8.5.5 [dcl.init] _zero-initialize_
Réalisé dans les cas suivants
2. Type d'initialiseur: 8.5.6 [dcl.init] _default-initialize_
Réalisé dans les cas suivants
3. Type d'initialiseur: 8.5.7 [dcl.init] _value-initialize_
Donc pour résumer
la source
Je peux confirmer qu'en C ++ 11, tout ce qui est mentionné dans la question sous C ++ 14 est correct, au moins selon les implémentations du compilateur.
Afin de vérifier cela, j'ai ajouté le code suivant à ma suite de tests . J'ai testé avec
-std=c++11 -O3
GCC 7.4.0, GCC 5.4.0, Clang 10.0.1 et VS 2017, et tous les tests ci-dessous réussissent.#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(); }
Les endroits où
UB!
est mentionné sont des comportements indéfinis, et le comportement réel dépendra probablement de nombreux facteurs (a.m
peut être égal à 42, 0 ou une autre poubelle). Les endroits où~UB
est mentionné sont également des comportements indéfinis en théorie, mais en pratique, en raison de l'utilisation d'un placement nouveau, il est très peu probable quea->m
ce soit égal à autre chose que 42.la source