Standard-, Wert- und Null-Initialisierungs-Chaos

87

Ich bin sehr verwirrt über Wert- & Standard- & Null-Initialisierung. und vor allem, wenn sie sich für die verschiedenen Standards C ++ 03 und C ++ 11 (und C ++ 14 ) einsetzen.

Ich zitiere und versuche, eine wirklich gute Antwort Value- / Default- / Zero-Init C ++ 98 und C ++ 03 hier zu erweitern, um sie allgemeiner zu gestalten, da es vielen Benutzern helfen würde, wenn jemand beim Ausfüllen helfen könnte Benötigten Sie Lücken, um einen guten Überblick darüber zu erhalten, was wann passiert?

Der vollständige Einblick anhand von Beispielen auf den Punkt gebracht:

Manchmal wird der vom neuen Operator zurückgegebene Speicher initialisiert, und manchmal hängt es nicht davon ab, ob der Typ, den Sie neu erstellen, ein ist POD (einfache alte Daten) ist oder ob es sich um eine Klasse handelt, die POD-Mitglieder enthält und eine verwendet Vom Compiler generierter Standardkonstruktor.

  • In C ++ 1998 gibt es zwei Arten der Initialisierung: Null- und Standardinitialisierung
  • In C ++ 2003 wurde eine dritte Art der Initialisierung, die Wertinitialisierung, hinzugefügt.
  • In C ++ 2011 / C ++ 2014 nur list-Initialisierung wurde hinzugefügt und die Regeln für die wert- / Default- / Null-Initialisierung ein wenig verändert.

Annehmen:

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 */

In einem C ++ 98-Compiler sollte Folgendes auftreten :

  • new A - unbestimmter Wert ( Aist POD)
  • new A()- Nullinitialisierung
  • new B - Standardkonstrukt ( B::mist nicht initialisiert, Bist kein POD)
  • new B()- Standardkonstrukt ( B::mist nicht initialisiert)
  • new C - Standardkonstrukt ( C::mist nullinitialisiert, Ckein POD)
  • new C() - Standardkonstrukt (C::m ist nullinitialisiert)
  • new D - Standardkonstrukt ( D::mist nicht initialisiert,Dist kein POD)
  • new D()- Standardkonstrukt? ( D::mist nicht initialisiert)

In einem C ++ 03-konformen Compiler sollten die Dinge so funktionieren:

  • new A - unbestimmter Wert (A ist POD)
  • new A() - Wertinitialisierung A , dh Nullinitialisierung, da es sich um einen POD handelt.
  • new B - Standardinitialisierung (lässt B::mnicht initialisiert,B ist kein POD)
  • new B() - Wert initialisiert B dem alle Felder mit Null initialisiert werden, da der Standard-ctor vom Compiler generiert wird und nicht benutzerdefiniert.
  • new C - default-initializes C, der den Standard-ctor aufruft. ( C::mist nullinitialisiert, Ckein POD)
  • new C() - value-initializes C, der den Standard-ctor aufruft. ( C::mist nullinitialisiert)
  • new D - Standardkonstrukt ( D::mist nicht initialisiert, Dist kein POD)
  • new D() - Wert initialisiert D? , der den Standard-Ctor aufruft ( D::mist nicht initialisiert)

Kursive Werte und? Sind Unsicherheiten, bitte helfen Sie dies zu korrigieren :-)

In einem C ++ 11-konformen Compiler sollten die Dinge so funktionieren:

??? (Bitte helfen Sie, wenn ich hier anfange, wird es sowieso schief gehen)

In einem C ++ 14-konformen Compiler sollten die Dinge so funktionieren: ??? (Bitte helfen Sie, wenn ich hier anfange, wird es trotzdem schief gehen) (Entwurf basierend auf Antwort)

  • new A - Standard-Initialisierungen A, Compiler-Gen. ctor, (verlässt A::mnicht initialisiert) ( Aist POD)

  • new A() - Wertinitialisierung, dh NullinitialisierungA seit 2. Punkt in [dcl.init] / 8

  • new B - Standard-Initialisierungen B, Compiler-Gen. ctor, (verlässt B::mnicht initialisiert) ( Bist kein POD)

  • new B() - value-initializes, beiB dem alle Felder mit Null initialisiert werden, da der Standard-ctor vom Compiler generiert wird und nicht benutzerdefiniert.

  • new C - default-initializes C, der den Standard-ctor aufruft. ( C::mist nullinitialisiert, Ckein POD)

  • new C() - value-initializes C, der den Standard-ctor aufruft. ( C::mist nullinitialisiert)

  • new D - Standardinitialisierungen D( D::mist nicht initialisiert, Dist kein POD)

  • new D() - value-initializes D, der den Standard-ctor aufruft ( D::mnicht initialisiert)

  • new E - default-initializes E, wodurch der Comp aufgerufen wird. gen. ctor. ( E::mist nicht initialisiert, E ist kein POD)

  • new E() - WertinitialisierungenE , die Eseit 2 Punkten in [dcl.init] / 8 ) mit Null initialisiert werden )

  • new F - default-initializes F, wodurch der Comp aufgerufen wird. gen. ctor. ( F::mist nicht initialisiert, Fist kein POD)

  • new F() - value-initializes F, die seit dem 1. Punkt in [dcl.init] / 8 standardmäßig initialisiert werden ( ctor-Funktion wird vom Benutzer bereitgestellt, wenn sie vom Benutzer deklariert und bei ihrer ersten Deklaration nicht explizit standardmäßig standardisiert oder gelöscht wird. Link )FF

Gabriel
quelle
Hier gibt es eine gute Erklärung dafür: en.cppreference.com/w/cpp/language/default_constructor
Richard Hodges
1
Soweit ich das beurteilen kann, gibt es in diesen Beispielen nur einen Unterschied zwischen C ++ 98 und C ++ 03. Das Problem scheint in N1161 (es gibt spätere Überarbeitungen dieses Dokuments) und CWG DR # 178 beschrieben zu sein . Der Wortlaut musste in C ++ 11 aufgrund neuer Funktionen und einer neuen POD-Spezifikation geändert werden. In C ++ 14 wurde er aufgrund von Fehlern im C ++ 11-Wortlaut erneut geändert. Die Auswirkungen in diesen Fällen werden jedoch nicht geändert .
Dyp
3
Während es langweilig ist, struct D { D() {}; int m; };kann es sich lohnen, es in Ihre Liste aufzunehmen.
Yakk - Adam Nevraumont

Antworten:

24

C ++ 14 spezifiziert die Initialisierung von Objekten, die mit newin [expr.new] / 17 erstellt wurden ([expr.new] / 15 in C ++ 11, und die Notiz war damals keine Notiz, sondern normativer Text):

Ein neuer Ausdruck , der ein Objekt vom Typ erstellt, Tinitialisiert dieses Objekt wie folgt:

  • Wenn der Neuinitialisierer weggelassen wird, wird das Objekt standardmäßig initialisiert (8.5). [ Hinweis: Wenn keine Initialisierung durchgeführt wird, hat das Objekt einen unbestimmten Wert. - Endnote ]
  • Andernfalls wird der Neuinitialisierer gemäß den Initialisierungsregeln von 8.5 für die Direktinitialisierung interpretiert .

Die Standardinitialisierung ist in [dcl.init] / 7 definiert (/ 6 in C ++ 11, und der Wortlaut selbst hat den gleichen Effekt):

Das Standardinitialisieren eines Objekts vom Typ Tbedeutet:

  • Wenn Tes sich um einen (möglicherweise cv-qualifizierten) Klassentyp handelt (Abschnitt 9), wird der Standardkonstruktor (12.1) für Taufgerufen (und die Initialisierung ist fehlerhaft, wennT kein Standardkonstruktor vorhanden ist oder die Überlastungsauflösung (13.3) zu einer Mehrdeutigkeit oder zu führt eine Funktion, die aus dem Kontext der Initialisierung gelöscht wurde oder auf die nicht zugegriffen werden kann);
  • Wenn Tes sich um einen Array-Typ handelt, wird jedes Element standardmäßig initialisiert .
  • Andernfalls wird keine Initialisierung durchgeführt.

So

  • new Abewirkt lediglich, dass der AStandardkonstruktor s aufgerufen wird, der nicht initialisiert wird m. Unbestimmter Wert. Sollte das gleiche sein für new B.
  • new A() wird gemäß [dcl.init] / 11 (/ 10 in C ++ 11) interpretiert:

    Ein Objekt, dessen Initialisierer ein leerer Satz von Klammern ist, dh (), muss wertinitialisiert werden.

    Und nun betrachten Sie [dcl.init] / 8 (/ 7 in C ++ 11 †):

    Um Wert initialisieren ein Objekt des Typs Tbedeutet:

    • Wenn Tes sich um einen (möglicherweise lebenslaufqualifizierten) Klassentyp (Abschnitt 9) ohne Standardkonstruktor (12.1) oder um einen vom Benutzer bereitgestellten oder gelöschten Standardkonstruktor handelt, wird das Objekt standardmäßig initialisiert.
    • Wenn Tes sich um einen (möglicherweise cv-qualifizierten) Klassentyp ohne einen vom Benutzer bereitgestellten oder gelöschten Standardkonstruktor handelt, wird das Objekt mit Null initialisiert und die semantischen Einschränkungen für die Standardinitialisierung werden überprüft. Wenn T einen nicht trivialen Standardkonstruktor hat, Das Objekt ist standardmäßig initialisiert.
    • Wenn Tes sich um einen Array-Typ handelt, wird jedes Element mit einem Wert initialisiert.
    • Andernfalls wird das Objekt mit Null initialisiert.

    Daher new A()wird Null initialisiert m. Und das sollte für Aund gleichwertig sein B.

  • new Cund new C()initialisiert das Objekt standardmäßig erneut, da der erste Aufzählungspunkt aus dem letzten Anführungszeichen gilt (C verfügt über einen vom Benutzer bereitgestellten Standardkonstruktor!). mIn beiden Fällen wird jetzt eindeutig im Konstruktor initialisiert.


† Nun, dieser Absatz hat in C ++ 11 einen etwas anderen Wortlaut, was das Ergebnis nicht ändert:

Um Wert initialisieren ein Objekt des Typs Tbedeutet:

  • Wenn Tes sich um einen (möglicherweise lebenslaufqualifizierten) Klassentyp (Abschnitt 9) mit einem vom Benutzer bereitgestellten Konstruktor (12.1) handelt, wird der Standardkonstruktor für T aufgerufen (und die Initialisierung ist fehlerhaft, wenn T keinen zugänglichen Standardkonstruktor hat).
  • Wenn Tes sich um einen (möglicherweise lebenslaufqualifizierten) Nicht-Union-Klassentyp ohne einen vom Benutzer bereitgestellten Konstruktor handelt, wird das Objekt mit Null initialisiert, und wenn Tder implizit deklarierte Standardkonstruktor nicht trivial ist, wird dieser Konstruktor aufgerufen.
  • Wenn Tes sich um einen Array-Typ handelt, wird jedes Element mit einem Wert initialisiert.
  • Andernfalls wird das Objekt mit Null initialisiert.
Columbo
quelle
@ Gabriel nicht wirklich.
Columbo
Ah, Sie sprechen also hauptsächlich über C ++ 14 und Referenzen für C ++ 11 sind in Klammern angegeben
Gabriel
@ Gabriel Richtig. Ich meine, C ++ 14 ist der neueste Standard, das steht also im Vordergrund.
Columbo
1
Das Ärgerliche beim Versuch, Initialisierungsregeln über Standards hinweg zu verfolgen, ist, dass viele der Änderungen (die meisten? Alle?) Zwischen den veröffentlichten C ++ 14- und C ++ 11-Standards über DRs vorgenommen wurden, und de facto auch C ++ 11 . Und dann gibt es auch Post-C ++ 14 DRs ...
TC
@Columbo Ich verstehe immer noch nicht, warum struct A { int m; }; struct C { C() : m(){}; int m; };unterschiedliche Ergebnisse erzeugt werden und warum m in A überhaupt initialisiert wird. Ich habe einen speziellen Thread für das Experiment geöffnet, das ich durchgeführt habe, und ich werde mich über Ihre Beiträge dort freuen, um das Problem zu klären. Vielen Dank stackoverflow.com/questions/45290121/…
darkThoughts
12

Die folgende Antwort erweitert die Antwort https://stackoverflow.com/a/620402/977038 die als Referenz für C ++ 98 und C ++ 03 dienen würde

Die Antwort zitieren

  1. In C ++ 1998 gibt es zwei Arten der Initialisierung: Null und Standard
  2. In C ++ 2003, einem dritten Initialisierungstyp, wurde die Wertinitialisierung hinzugefügt.

C ++ 11 (In Bezug auf n3242)

Initialisierer

8.5 Initialisierer [dcl.init] Gibt an, daß eine Variable oder nicht POD POD kann entweder als initialisiert wird Klammer-oder-gleich-Initialisierer der entweder sein kann verspannt-init-Liste oder Initialisierer-Klausel aggregately bezeichnet als Klammer-or-gleichschenkligen Initialisierer oder using (Ausdrucksliste) . Vor C ++ 11 wurde nur (Ausdrucksliste) oder Initialisierer-Klausel unterstützt, obwohl die Initialisierer-Klausel eingeschränkter war als in C ++ 11. In C ++ 11 unterstützt die initializer-Klausel jetzt die Klammer-Init-Liste, abgesehen vom Zuweisungsausdruckwie in C ++ 03. Die folgende Grammatik fasst die neue unterstützte Klausel zusammen, bei der der fett gedruckte Teil im C ++ 11-Standard neu hinzugefügt wurde.

Initialisierer:
    Klammer-oder-gleich-Initialisierer
    (Ausdrucksliste)
Klammer-oder-gleich-Initialisierer:
    = Initialisierer-Klausel Klammer-
    Init-Liste
Initialisierer-Klausel:
    Zuweisungsausdruck Klammer
    -Init-Liste
Initialisierer-Liste:
    Initialisierer-Klausel ... opt
    Initialisierer-Liste, Initialisierer-Klausel ... opt **
Klammer-Init-Liste:
    {Initialisierer-Liste, opt}
    {}

Initialisierung

Wie C ++ 03 unterstützt C ++ 11 weiterhin drei Formen der Initialisierung


Hinweis

Der fett hervorgehobene Teil wurde in C ++ 11 hinzugefügt, und der durchgestrichene Teil wurde aus C ++ 11 entfernt.

  1. Initialisierungstyp: 8.5.5 [dcl.init] _zero-initialize_

Wird in den folgenden Fällen durchgeführt

  • Objekte mit statischer oder Thread-Speicherdauer werden mit Null initialisiert
  • Wenn weniger Initialisierer als Array-Elemente vorhanden sind, muss jedes nicht explizit initialisierte Element mit Null initialisiert werden
  • Wenn während der Wertinitialisierung T ein (möglicherweise lebenslaufqualifizierter) Nicht-Vereinigungsklassentyp ohne einen vom Benutzer bereitgestellten Konstruktor ist, wird das Objekt mit Null initialisiert.

Ein Objekt oder eine Referenz vom Typ T auf Null zu initialisieren bedeutet:

  • Wenn T ein Skalartyp ist (3.9), wird das Objekt auf den Wert 0 (Null) gesetzt, der als integraler konstanter Ausdruck in T konvertiert wird.
  • Wenn T ein (möglicherweise cv-qualifizierter) Nicht-Vereinigungsklassentyp ist, wird jedes nicht statische Datenelement und jedes Basisklassen-Unterobjekt mit Null initialisiert und das Auffüllen mit Null Bits initialisiert.
  • Wenn T ein (möglicherweise lebenslaufqualifizierter) Vereinigungstyp ist, wird das erste nicht statisch benannte Datenelement des Objekts mit Null initialisiert und das Auffüllen mit Null Bits initialisiert.
  • Wenn T ein Array-Typ ist, wird jedes Element mit Null initialisiert.
  • Wenn T ein Referenztyp ist, wird keine Initialisierung durchgeführt.

2. Initialisierungstyp: 8.5.6 [dcl.init] _default-initialize_

Wird in den folgenden Fällen durchgeführt

  • Wenn der Neuinitialisierer weggelassen wird, wird das Objekt standardmäßig initialisiert. Wenn keine Initialisierung durchgeführt wird, hat das Objekt einen unbestimmten Wert.
  • Wenn für ein Objekt kein Initialisierer angegeben ist, wird das Objekt standardmäßig initialisiert, mit Ausnahme von Objekten mit statischer oder Thread-Speicherdauer
  • Wenn eine Basisklasse oder ein nicht statisches Datenelement in einer Konstruktorinitialisiererliste nicht erwähnt wird und dieser Konstruktor aufgerufen wird.

Die Standardinitialisierung eines Objekts vom Typ T bedeutet:

  • Wenn T ein (möglicherweise cv-qualifizierter) Nicht-POD- Klassentyp ist (Abschnitt 9), wird der Standardkonstruktor für T aufgerufen (und die Initialisierung ist fehlerhaft, wenn T keinen zugänglichen Standardkonstruktor hat).
  • Wenn T ein Array-Typ ist, wird jedes Element standardmäßig initialisiert.
  • Andernfalls wird keine Initialisierung durchgeführt.

Hinweis Bis C ++ 11 wurden nur Nicht-POD-Klassentypen mit automatischer Speicherdauer als standardinitialisiert betrachtet, wenn kein Initialisierer verwendet wird.


3. Initialisierungstyp: 8.5.7 [dcl.init] _value-initialize_

  1. Wenn ein Objekt (namenlose temporäre, benannte Variable, dynamische Speicherdauer oder nicht statisches Datenelement), dessen Initialisierer ein leerer Satz von Klammern ist, dh () oder geschweifte Klammern {}

Ein Objekt vom Typ T wertinitialisieren bedeutet:

  • Wenn T ein (möglicherweise lebenslaufqualifizierter) Klassentyp (Abschnitt 9) mit einem vom Benutzer bereitgestellten Konstruktor (12.1) ist, wird der Standardkonstruktor für T aufgerufen (und die Initialisierung ist fehlerhaft, wenn T keinen zugänglichen Standardkonstruktor hat). ;;
  • Wenn T ein (möglicherweise lebenslaufqualifizierter) Nicht-Union-Klassentyp ohne einen vom Benutzer bereitgestellten Konstruktor ist, wird jedes nicht statische Datenelement und jede Basisklassenkomponente von T wertinitialisiert. dann wird das Objekt mit Null initialisiert, und wenn der implizit deklarierte Standardkonstruktor von T nicht trivial ist, wird dieser Konstruktor aufgerufen.
  • Wenn T ein Array-Typ ist, wird jedes Element mit einem Wert initialisiert.
  • Andernfalls wird das Objekt mit Null initialisiert.

Also um es zusammenzufassen

Hinweis Das entsprechende Zitat aus dem Standard ist fett hervorgehoben

  • new A: default-initializes (lässt A :: m nicht initialisiert)
  • new A (): Nullinitialisierung A, da der wertinitialisierte Kandidat keinen vom Benutzer bereitgestellten oder gelöschten Standardkonstruktor hat. Wenn T ein (möglicherweise cv-qualifizierter) Nicht-Union-Klassentyp ohne einen vom Benutzer bereitgestellten Konstruktor ist, wird das Objekt mit Null initialisiert, und wenn der implizit deklarierte Standardkonstruktor von T nicht trivial ist, wird dieser Konstruktor aufgerufen.
  • neues B: Standardinitialisierungen (lässt B :: m nicht initialisiert)
  • neues B (): Wert initialisiert B, wodurch alle Felder auf Null initialisiert werden; Wenn T ein (möglicherweise lebenslaufqualifizierter) Klassentyp (Abschnitt 9) mit einem vom Benutzer bereitgestellten Konstruktor (12.1) ist, wird der Standardkonstruktor für T aufgerufen
  • neues C: Standardinitialisiert C, das den Standard-Ctor aufruft. Wenn T ein (möglicherweise lebenslaufqualifizierter) Klassentyp ist (Abschnitt 9), wird der Standardkonstruktor für T aufgerufen . Wenn der neue Initialisierer weggelassen wird, wird das Objekt standardmäßig initialisiert
  • new C (): value initialisiert C, das den Standard-ctor aufruft. Wenn T ein (möglicherweise lebenslaufqualifizierter) Klassentyp (Abschnitt 9) mit einem vom Benutzer bereitgestellten Konstruktor (12.1) ist, wird der Standardkonstruktor für T aufgerufen. Darüber hinaus muss ein Objekt, dessen Initialisierer ein leerer Satz von Klammern ist, dh (), wertinitialisiert werden
Abhijit
quelle
0

Ich kann bestätigen, dass in C ++ 11 alles, was in der Frage unter C ++ 14 erwähnt wird, korrekt ist, zumindest gemäß den Compiler-Implementierungen.

Um dies zu überprüfen, habe ich meiner Testsuite den folgenden Code hinzugefügt . Ich habe mit -std=c++11 -O3in GCC 7.4.0, GCC 5.4.0, Clang 10.0.1 und VS 2017 getestet und alle folgenden Tests sind bestanden.

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

Die Orte, an denen UB!erwähnt wird, sind undefiniertes Verhalten, und das tatsächliche Verhalten hängt wahrscheinlich von vielen Faktoren ab ( a.mkann 42, 0 oder einem anderen Müll entsprechen). Die Orte, an denen ~UBerwähnt wird, sind auch theoretisch undefiniertes Verhalten, aber in der Praxis ist es aufgrund der Verwendung eines neuen Praktikums sehr unwahrscheinlich a->m, dass es irgendetwas anderem als 42 entspricht.

Boris Dalstein
quelle