Domyślna, wartość i zerowy bałagan inicjalizacji

88

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 ( Ato POD)
  • new A()- inicjalizacja zerowa
  • new B - domyślna konstrukcja (nie B::mjest zainicjowana, Bnie jest POD)
  • new B()- domyślna konstrukcja ( B::mjest niezainicjowana)
  • new C - domyślna konstrukcja ( C::mjest zainicjowana przez zero, Cnie jest POD)
  • new C()- domyślna konstrukcja ( C::mjest inicjalizowana przez zero)
  • new D - domyślna konstrukcja (nie D::mjest zainicjowana, Dnie jest POD)
  • new D()- domyślna konstrukcja? (nie D::mjest zainicjowany)

W kompilatorze zgodnym z C ++ 03 wszystko powinno wyglądać tak:

  • new A - wartość nieokreślona ( Ato POD)
  • new A() - inicjalizacja wartości A, która jest inicjalizacją zerową, ponieważ jest to POD.
  • new B - default-inicjalizuje (pozostawia B::mniezainicjowany, Bnie jest POD)
  • new B() - wartość-inicjalizuje, Bktóra inicjalizuje wszystkie pola zerem, ponieważ jego domyślny procesor jest generowany przez kompilator, a nie zdefiniowany przez użytkownika.
  • new C - default-initializes C, co wywołuje domyślny ctor. ( C::mjest zainicjowany przez zero, Cnie jest POD)
  • new C() - inicjalizuje wartość C, która wywołuje domyślny ctor. ( C::mjest zainicjowany przez zero)
  • new D - domyślna konstrukcja (nie D::mjest zainicjowana, Dnie jest POD)
  • new D() - wartość-inicjalizuje D? , który wywołuje domyślnego ctora ( D::mjest 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-inicjalizuje A, kompilator gen. ctor, (odchodzi A::mniezainicjalizowany) ( Ajest POD)

  • new A() - inicjalizacja wartości A, która jest inicjalizacją zerową od 2. punktu w [dcl.init] / 8

  • new B - default-inicjalizuje B, kompilator gen. ctor, (opuszcza B::mniezainicjalizowany) ( Bnie jest POD)

  • new B() - wartość-inicjalizuje, Bktóra inicjalizuje wszystkie pola zerem, ponieważ jego domyślny procesor jest generowany przez kompilator, a nie zdefiniowany przez użytkownika.

  • new C - default-initializes C, co wywołuje domyślny ctor. ( C::mjest zainicjowany przez zero, Cnie jest POD)

  • new C() - inicjalizuje wartość C, która wywołuje domyślny ctor. ( C::mjest zainicjowany przez zero)

  • new D - default-inicjalizuje D(nie D::mjest zainicjowany , Dnie jest POD)

  • new D() - inicjalizuje wartość D, która wywołuje domyślny ctor ( D::mjest niezainicjowany)

  • new E - default-inicjalizuje E, co wywołuje komp. gen. ctor. (nie E::mjest zainicjowany, E nie jest POD)

  • new E() - inicjalizuje wartość E, która inicjalizuje zero Eod 2 punktu w [dcl.init] / 8 )

  • new F - default-inicjalizuje F, co wywołuje komp. gen. ctor. (nie F::mjest zainicjowany, Fnie jest POD)

  • new F() - inicjalizuje wartość F, która inicjalizuje się domyślnie F od 1. punktu w [dcl.init] / 8 ( Ffunkcja 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 )

Gabriel
źródło
tutaj jest dobre wyjaśnienie: en.cppreference.com/w/cpp/language/default_constructor
Richard Hodges
1
O ile wiem, w tych przykładach istnieje tylko różnica między C ++ 98 a C ++ 03. Wydaje się, że problem został opisany w N1161 (istnieją późniejsze wersje tego dokumentu) i CWG DR # 178 . Sformułowanie potrzebne do zmian w C ++ 11 ze względu na nowe funkcje i nową specyfikacją POD, a to znowu zmieniło w C ++ 14 z powodu wad w c ++ 11 brzmienia, ale efekty w tych przypadkach, które nie zostały zmienione .
dyp
3
Chociaż nudne, struct D { D() {}; int m; };może warto umieścić je na liście.
Yakk - Adam Nevraumont

Odpowiedzi:

24

C ++ 14 określa inicjalizację obiektów utworzonych za pomocą neww [wyr.new] / 17 ([wyr.new] / 15 w C ++ 11, a notatka nie była wówczas notatką, ale tekstem normatywnym):

Nowa ekspresja , która tworzy obiekt typu Tinicjuje tego obiektu w następujący sposób:

  • Jeśli pominięto nowy inicjator , obiekt jest inicjalizowany domyślnie (8.5). [ Uwaga: Jeśli inicjalizacja nie jest wykonywana, obiekt ma nieokreśloną wartość. - notatka końcowa ]
  • W przeciwnym razie nowy inicjator jest interpretowany zgodnie z regułami inicjalizacji z wersji 8.5 dla inicjalizacji bezpośredniej .

Inicjalizacja domyślna jest zdefiniowana w [dcl.init] / 7 (/ 6 w C ++ 11, a samo sformułowanie ma ten sam efekt):

Aby domyślnie zainicjować obiektu typu Tśrodków:

  • jeśli Tjest (prawdopodobnie kwalifikowany przez cv) typem klasy (klauzula 9), Twywoływany jest domyślny konstruktor (12.1) for (a inicjalizacja jest źle sformułowana, jeśli Tnie ma domyślnego konstruktora lub rozwiązania przeciążenia (13.3) powoduje niejednoznaczność lub funkcja, która jest usunięta lub niedostępna w kontekście inicjalizacji);
  • jeśli Tjest typem tablicowym, każdy element jest inicjalizowany domyślnie ;
  • w przeciwnym razie inicjalizacja nie jest wykonywana.

A zatem

  • new Apowoduje jedynie Awywołanie domyślnego konstruktora, który nie jest inicjowany m. Wartość nieokreślona. Powinien być taki sam dla new B.
  • new A() jest interpretowany zgodnie z [dcl.init] / 11 (/ 10 w C ++ 11):

    Obiekt, którego inicjatorem jest pusty zestaw nawiasów, tj. (), Powinien zostać zainicjalizowany wartością.

    A teraz rozważ [dcl.init] / 8 (/ 7 w C ++ 11 †):

    Aby zainicjować wartość obiektu typu, Toznacza to:

    • jeśli Tjest (prawdopodobnie kwalifikowaną przez cv) typem klasy (klauzula 9) bez domyślnego konstruktora (12.1) lub domyślnego konstruktora dostarczonego lub usuniętego przez użytkownika, to obiekt jest inicjalizowany domyślnie;
    • Jeśli T jest typem klasy (prawdopodobnie kwalifikowanym przez cv) bez konstruktora domyślnego dostarczonego przez użytkownika lub usuniętego, to obiekt jest inicjowany przez zero i są sprawdzane ograniczenia semantyczne dla inicjalizacji domyślnej, a jeśli T ma nietrywialny konstruktor domyślny, obiekt jest inicjalizowany domyślnie;
    • jeśli Tjest typem tablicowym, to każdy element jest inicjowany wartością;
    • w przeciwnym razie obiekt jest inicjowany przez zero.

    Stąd new A()inicjalizacja zerowa m. Powinno to być równoważne dla Ai B.

  • new Ci new 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źniej mw obu przypadkach now jest inicjowany w konstruktorze.


† Cóż, ten akapit ma nieco inne brzmienie w C ++ 11, co nie zmienia wyniku:

Aby zainicjować wartość obiektu typu, Toznacza to:

  • jeśli Tjest to typ klasy (prawdopodobnie cv kwalifikowany) (klauzula 9) z konstruktorem dostarczonym przez użytkownika (12.1), T to wywoływany jest domyślny konstruktor for (a inicjalizacja jest źle sformułowana, jeśli T nie ma dostępnego domyślnego konstruktora);
  • jeśli Tjest (prawdopodobnie z kwalifikacją cv) typem klasy nieunijnej bez konstruktora dostarczonego przez użytkownika, to obiekt jest inicjowany przez zero i, jeśli Tdomyślny konstruktor zadeklarowany niejawnie jest nietrywialny, wywoływany jest ten konstruktor.
  • jeśli Tjest typem tablicowym, to każdy element jest inicjalizowany wartością;
  • w przeciwnym razie obiekt jest inicjowany przez zero.
Columbo
źródło
@Gabriel nie bardzo.
Columbo
ach, więc mówisz głównie o c ++ 14, a odwołania do c ++ 11 są podane w nawiasach
Gabriel
@Gabriel Correct. Mam na myśli, że C ++ 14 to najnowszy standard, więc to jest na pierwszy plan.
Columbo,
1
Irytujące w próbach śledzenia reguł inicjalizacji w różnych standardach jest to, że wiele zmian (większość?) Między opublikowanymi standardami C ++ 14 i C ++ 11 nastąpiło za pośrednictwem DR, a więc de facto C ++ 11 . Są też wersje DR po C ++ 14 ...
TC
@Columbo Nadal nie rozumiem, dlaczego 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/…
darkThoughts
12

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ź

  1. W C ++ 1998 istnieją 2 typy inicjalizacji: zerowa i domyślna
  2. W C ++ 2003 dodano trzeci typ inicjalizacji, czyli inicjalizację wartości.

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

Część wyróżniona pogrubioną czcionką została dodana w C ++ 11, a ta, która jest przekreślona, ​​została usunięta z C ++ 11.

  1. Typ inicjalizatora: 8.5.5 [dcl.init] _zero-initialize_

Wykonano w następujących przypadkach

  • Obiekty ze statycznym lub wątkowym czasem trwania są inicjowane przez zero
  • Jeśli jest mniej inicjatorów niż elementów tablicy, każdy element, który nie został jawnie zainicjowany, powinien być zainicjowany przez zero
  • Podczas inicjowania wartości , jeśli T jest (prawdopodobnie kwalifikowaną przez cv) typem klasy nieunijnej bez konstruktora dostarczonego przez użytkownika, obiekt jest inicjowany przez zero.

Aby wyzerować obiekt lub odwołanie typu T oznacza:

  • jeśli T jest typem skalarnym (3.9), obiekt jest ustawiany na wartość 0 (zero), traktowaną jako całkowe wyrażenie stałe , konwertowane na T;
  • jeśli T jest (być może kwalifikowanym przez cv) typem klasy nie-unii, każdy niestatyczny element członkowski danych i każdy podobiekt klasy bazowej jest inicjowany przez zero, a wypełnienie jest inicjowane do zera bitów;
  • jeśli T jest typem unii (prawdopodobnie kwalifikowanym przez cv) , pierwszy niestatyczny nazwany element danych obiektu ma wartość zerową, a dopełnienie jest inicjowane na zero bitów;
  • jeśli T jest typem tablicowym, każdy element jest inicjowany przez zero;
  • jeśli T jest typem referencyjnym, inicjalizacja nie jest wykonywana.

2. Typ inicjalizatora: 8.5.6 [dcl.init] _default-initialize_

Wykonano w następujących przypadkach

  • Jeśli pominięto nowy inicjator, obiekt jest inicjalizowany domyślnie; jeśli nie zostanie przeprowadzona inicjalizacja, obiekt ma nieokreśloną wartość.
  • Jeśli dla obiektu nie określono inicjatora, obiekt jest inicjowany domyślnie, z wyjątkiem obiektów ze statycznym lub wątkowym czasem trwania
  • Gdy klasa bazowa lub niestatyczny element członkowski danych nie jest wymieniony na liście inicjatorów konstruktora i wywoływany jest ten konstruktor.

Domyślna inicjalizacja obiektu typu T oznacza:

  • jeśli T jest (prawdopodobnie kwalifikowaną przez cv) klasą inną niż POD (klauzula 9), wywoływany jest domyślny konstruktor dla T (a inicjalizacja jest źle sformułowana, jeśli T nie ma dostępnego domyślnego konstruktora);
  • jeśli T jest typem tablicowym, każdy element jest inicjalizowany domyślnie;
  • w przeciwnym razie inicjalizacja nie jest wykonywana.

Uwaga: Do C ++ 11 tylko typy klas innych niż POD z automatycznym czasem przechowywania były uznawane za inicjowane domyślnie, gdy nie jest używany inicjator.


3. Typ inicjalizatora: 8.5.7 [dcl.init] _value-initialize_

  1. Gdy obiekt (tymczasowa bezimienna, nazwana zmienna, dynamiczny czas trwania lub niestatyczna składowa danych), którego inicjatorem jest pusty zestaw nawiasów, tj. () Lub nawiasy klamrowe {}

Inicjalizacja wartości obiektu typu T oznacza:

  • jeśli T jest (prawdopodobnie kwalifikowaną przez cv) typem klasy (klauzula 9) z konstruktorem dostarczonym przez użytkownika (12.1), wywoływany jest domyślny konstruktor dla T (a inicjalizacja jest źle sformułowana, jeśli T nie ma dostępnego domyślnego konstruktora) ;
  • jeśli T jest (prawdopodobnie kwalifikowanym przez cv) typem klasy nieunijnym bez konstruktora dostarczonego przez użytkownika, to każdy niestatyczny element członkowski danych i składnik klasy bazowej T jest inicjowany wartością; wtedy obiekt jest inicjowany przez zero i, jeśli domyślny konstruktor T zadeklarowany niejawnie jest nietrywialny, wywoływany jest ten konstruktor.
  • jeśli T jest typem tablicowym, to każdy element jest inicjalizowany wartością;
  • w przeciwnym razie obiekt jest inicjowany przez zero.

Podsumowując

Uwaga Odpowiedni cytat z normy jest pogrubiony

  • nowy A: default-initializes (pozostawia A :: m niezainicjalizowany)
  • new A (): Zeruj inicjalizację A, ponieważ zainicjowana wartość kandydata nie ma dostarczonego przez użytkownika ani usuniętego konstruktora domyślnego. jeśli T jest (być może kwalifikowanym przez cv) typem klasy nieunijnej bez konstruktora dostarczonego przez użytkownika, to obiekt jest inicjowany przez zero i, jeśli niejawnie zadeklarowany konstruktor domyślny T jest nietrywialny, wywoływany jest ten konstruktor.
  • nowy B: default-initializes (pozostawia B :: m niezainicjalizowany)
  • nowe B (): wartość-inicjalizuje B, która zeruje-inicjalizuje wszystkie pola; jeśli T jest (prawdopodobnie kwalifikowaną przez cv) typem klasy (klauzula 9) z konstruktorem dostarczonym przez użytkownika (12.1), wówczas wywoływany jest domyślny konstruktor dla T
  • nowy C: default-inicjalizuje C, który wywołuje domyślnego ctora. jeśli T jest (prawdopodobnie kwalifikowaną przez cv) typem klasy (klauzula 9), wywoływany jest domyślny konstruktor dla T , Ponadto jeśli pominięto inicjator nowego, obiekt jest inicjalizowany domyślnie
  • nowe C (): wartość-inicjalizuje C, które wywołuje domyślny ctor. jeśli T jest (prawdopodobnie kwalifikowaną przez cv) typem klasy (klauzula 9) z konstruktorem dostarczonym przez użytkownika (12.1), wywoływany jest domyślny konstruktor dla T. Ponadto obiekt, którego inicjatorem jest pusty zestaw nawiasów, tj. (), Powinien zostać zainicjalizowany wartością
Abhijit
źródło
0

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 -O3w 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.mmoże być równe 42, 0 lub innym śmieciom). Miejsca, o których ~UBwspomina 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.

Boris Dalstein
źródło