По умолчанию, значение и нулевой беспорядок инициализации

88

Я очень запутался в инициализации значений, значений по умолчанию и нуля. и особенно когда они вступают в силу различных стандартов C ++ 03 и C ++ 11C ++ 14 ).

Я цитирую и пытаюсь расширить действительно хороший ответ Value- / Default- / Zero- Init C ++ 98 и C ++ 03 здесь, чтобы сделать его более общим, поскольку это помогло бы многим пользователям, если бы кто-то мог помочь заполнить нужны пробелы, чтобы иметь хорошее представление о том, что и когда происходит?

Полный анализ примеров вкратце:

Иногда память, возвращаемая оператором new, будет инициализирована, а иногда это не будет зависеть от того, является ли тип, который вы обновляете, POD (простые старые данные) , или если это класс, который содержит члены POD и использует конструктор по умолчанию, созданный компилятором.

  • В C ++ 1998 есть 2 типа инициализации: инициализация нуля и инициализация по умолчанию.
  • В C ++ 2003 был добавлен 3-й тип инициализации - инициализация значения .
  • В C ++ 2011 / C ++ 2014 была добавлена только инициализация списка, а правила для инициализации значения / значения по умолчанию / нуля немного изменены.

Предполагать:

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

В компиляторе C ++ 98 должно произойти следующее :

  • new A - неопределенное значение ( APOD)
  • new A()- нулевая инициализация
  • new B - конструкция по умолчанию ( B::mне инициализирована, не Bявляется POD)
  • new B()- конструкция по умолчанию ( B::mне инициализирована)
  • new C - конструкция по умолчанию ( C::mинициализирована нулем, не Cявляется POD)
  • new C()- конструкция по умолчанию ( C::mинициализируется нулем)
  • new D - конструкция по умолчанию ( D::mне инициализирована, не Dявляется POD)
  • new D()- конструкция по умолчанию? ( D::mне инициализирован)

В компиляторе, совместимом с C ++ 03, все должно работать так:

  • new A - неопределенное значение ( APOD)
  • new A() - value-initialize A, что является нулевой инициализацией, поскольку это POD.
  • new B - инициализируется по умолчанию (не B::mинициализируется, не Bявляется POD)
  • new B() - value-initializes, Bкоторый инициализирует нулем все поля, поскольку его ctor по умолчанию создается компилятором, а не определяемым пользователем.
  • new C - default-initializes C, который вызывает ctor по умолчанию. ( C::mинициализируется нулем, не Cявляется POD)
  • new C() - value-initializes C, который вызывает ctor по умолчанию. ( C::mинициализируется нулем)
  • new D - конструкция по умолчанию ( D::mне инициализирована, не Dявляется POD)
  • new D() - инициализирует значение D? , который вызывает ctor по умолчанию ( D::mне инициализирован)

Значения курсивом и? есть сомнения, пожалуйста, помогите исправить это :-)

В компиляторе, совместимом с C ++ 11, все должно работать так:

??? (пожалуйста, помогите, если я начну здесь, все равно пойдет не так)

В компиляторе, совместимом с C ++ 14, все должно работать так: ??? (пожалуйста, помогите, если я начну здесь, все равно пойдет не так) (Черновик на основе ответа)

  • new A - инициализируется по умолчанию A, компилятор gen. ctor, (оставляет A::mнеинициализированным) ( APOD)

  • new A() - value-initializes A, что является нулевой инициализацией с момента 2. точки в [dcl.init] / 8

  • new B - инициализируется по умолчанию B, компилятор gen. ctor, (оставляет B::mнеинициализированным) (не Bявляется POD)

  • new B() - value-initializes, Bкоторый инициализирует нулем все поля, поскольку его ctor по умолчанию создается компилятором, а не определяемым пользователем.

  • new C - default-initializes C, который вызывает ctor по умолчанию. ( C::mинициализируется нулем, не Cявляется POD)

  • new C() - value-initializes C, который вызывает ctor по умолчанию. ( C::mинициализируется нулем)

  • new D - инициализируется по умолчанию D( D::mне инициализируется, не Dявляется POD)

  • new D() - value-initializes D, который вызывает ctor по умолчанию ( D::mне инициализирован)

  • new E - default-initializes E, который вызывает comp. ген. ctor. ( E::mне инициализирован, E не POD)

  • new E() - значение-инициализирует E, который инициализируется нулем, Eначиная с 2 точки в [dcl.init] / 8 )

  • new F - default-initializes F, который вызывает comp. ген. ctor. ( F::mне инициализирован, не Fявляется POD)

  • new F() - инициализирует значение F, которое инициализируется по умолчанию, F начиная с 1. точки в [dcl.init] / 8 ( Fфункция ctor предоставляется пользователем, если она объявлена ​​пользователем и не используется явно по умолчанию или не удалена при первом объявлении. Ссылка )

Габриэль
источник
здесь есть хорошее объяснение: en.cppreference.com/w/cpp/language/default_constructor
Ричард Ходжес,
1
Насколько я могу судить, в этих примерах разница только между C ++ 98 и C ++ 03. Проблема, похоже, описана в N1161 (есть более поздние версии этого документа) и CWG DR # 178 . Формулировки , необходимые для изменения в C ++ 11 из - за новые функции и новую спецификацию POD, и он снова изменились в C ++ 14 из - за дефекты в C ++ 11 формулировок, но эффекты в этих случаях не изменились .
dyp
3
Хотя скучно, struct D { D() {}; int m; };возможно, стоит включить в свой список.
Якк - Адам Неврамонт

Ответы:

24

C ++ 14 определяет инициализацию объектов, созданных с помощью new[expr.new] / 17 ([expr.new] / 15 в C ++ 11, и тогда примечание было не примечанием, а нормативным текстом):

Новое выражение , которое создает объект типа Tинициализирует этот объект следующим образом :

  • Если новый инициализатор опущен, объект инициализируется по умолчанию (8.5). [ Примечание: если инициализация не выполняется, объект имеет неопределенное значение. - конец примечания ]
  • В противном случае новый инициализатор интерпретируется в соответствии с правилами инициализации 8.5 для прямой инициализации .

Инициализация по умолчанию определяется в [dcl.init] / 7 (/ 6 в C ++ 11, и сама формулировка имеет тот же эффект):

Чтобы по умолчанию инициализировать объект типа Tсредств:

  • если Tэто тип класса (возможно, cv-квалифицируемый) (раздел 9), Tвызывается конструктор по умолчанию (12.1) для (и инициализация плохо сформирована, если Tконструктор по умолчанию или разрешение перегрузки (13.3) отсутствует, приводит к неоднозначности или в функция, которая удалена или недоступна из контекста инициализации);
  • если Tэто тип массива, каждый элемент инициализируется по умолчанию ;
  • в противном случае инициализация не выполняется.

Таким образом

  • new Aвызывает только Aконструктор по умолчанию, который не инициализируется m. Неопределенное значение. Должно быть то же самое для new B.
  • new A() интерпретируется согласно [dcl.init] / 11 (/ 10 в C ++ 11):

    Объект, инициализатором которого является пустой набор скобок, т. Е. (), Должен быть инициализирован значением.

    А теперь рассмотрим [dcl.init] / 8 (/ 7 в C ++ 11 †):

    Для того, чтобы значение инициализации объекта типа Tсредств:

    • если Tэто тип класса (возможно, с квалификацией cv) (раздел 9) либо без конструктора по умолчанию (12.1), либо с конструктором по умолчанию, который предоставляется или удаляется пользователем, то объект инициализируется по умолчанию;
    • если Tэто (возможно, cv-квалифицированный) тип класса без предоставленного пользователем или удаленного конструктора по умолчанию, то объект инициализируется нулем и проверяются семантические ограничения для инициализации по умолчанию, и если T имеет нетривиальный конструктор по умолчанию, объект инициализируется по умолчанию;
    • если Tтип массива, то каждый элемент инициализируется значением;
    • в противном случае объект инициализируется нулем.

    Следовательно, new A()будет инициализироваться нулем m. И это должно быть эквивалентно для Aи B.

  • new Cи new C()снова инициализирует объект по умолчанию, поскольку применяется первая точка маркера из последней цитаты (C имеет конструктор по умолчанию, предоставленный пользователем!). Но, очевидно, mв обоих случаях в конструкторе инициализируется now .


† Что ж, этот абзац имеет несколько другую формулировку в C ++ 11, что не влияет на результат:

Для того, чтобы значение инициализации объекта типа Tсредств:

  • если Tэто тип класса (возможно, cv-квалифицируемый) (пункт 9) с конструктором, предоставленным пользователем (12.1), то T вызывается конструктор по умолчанию для (и инициализация плохо сформирована, если T не имеет доступного конструктора по умолчанию);
  • если Tэто (возможно, cv-квалифицированный) тип класса без объединения без предоставленного пользователем конструктора, то объект инициализируется нулем, и, если Tнеявно объявленный конструктор по умолчанию является нетривиальным, этот конструктор вызывается.
  • если Tтип массива, то каждый элемент инициализируется значением;
  • в противном случае объект инициализируется нулем.
Коломбо
источник
@ Габриэль, не совсем.
Коломбо
ах, так что вы говорите в основном о C ++ 14, а ссылки на C ++ 11 даны в скобках
Габриэль
@ Габриэль Верно. Я имею в виду, что C ++ 14 - это последний стандарт, так что это на первый план.
Коломбо
1
Раздражает то, что попытка проследить правила инициализации по стандартам заключается в том, что многие изменения (большинство? Все?) Между опубликованными стандартами C ++ 14 и C ++ 11 произошли через DR, как и де-факто C ++ 11 . А еще есть DR после C ++ 14 ...
TC
@Columbo Я до сих пор не понимаю, почему struct A { int m; }; struct C { C() : m(){}; int m; };получаются разные результаты и что вызывает инициализацию m в A в первую очередь. Я открыл специальную ветку для эксперимента, который я провел, и буду признателен за ваш вклад, чтобы прояснить проблему. Спасибо stackoverflow.com/questions/45290121/…
darkThoughts
12

Следующий ответ расширяет ответ https://stackoverflow.com/a/620402/977038, который будет служить ссылкой для C ++ 98 и C ++ 03.

Цитируя ответ

  1. В C ++ 1998 есть 2 типа инициализации: нулевая и по умолчанию.
  2. В C ++ 2003 был добавлен 3-й тип инициализации, инициализация значения.

C ++ 11 (применительно к n3242)

Инициализаторы

8.5 Инициализаторы [dcl.init] указывает, что переменная POD или не POD может быть инициализирована либо как фигурная скобка-или-равный-инициализатор, который может быть либо фигурным-инициализирующим списком, либо предложением инициализатора, совокупно именуемым фигурной-или-равным- инициализатор или с помощью (список-выражений) . До C ++ 11 поддерживалось только (список-выражений) или предложение- инициализатор, хотя предложение- инициализатор было более ограниченным, чем то, что есть в С ++ 11. В C ++ 11 предложение- инициализатор теперь поддерживает список-инициализации в фигурных скобках, кроме выражения-присваивания.как было в C ++ 03. Следующая грамматика резюмирует новое поддерживаемое предложение, в котором часть, выделенная жирным шрифтом, была недавно добавлена ​​в стандарт C ++ 11.

инициализатор:
    скобка или равно-инициализатор
    (выражение-лист)
бандаж или равно-инициализатор:
    = инициализатор-раздел
    приготовился-инициализировать-лист
инициализатор-п:
    назначение выражение
    приготовилось-INIT-лист
инициализатор-списка:
    инициализатор-раздел ... opt
    список-инициализаторов, предложение-инициализатора ... opt **
braced-init-list:
    {список-инициализаторов, opt}
    {}

Инициализация

Как и C ++ 03, C ++ 11 по-прежнему поддерживает три формы инициализации


Заметка

Часть, выделенная полужирным шрифтом, была добавлена ​​в C ++ 11, а та, которая была выделена, была удалена из C ++ 11.

  1. Тип инициализатора: 8.5.5 [dcl.init] _zero-initialize_

Выполняется в следующих случаях

  • Объекты со статической или поточной продолжительностью хранения инициализируются нулем
  • Если инициализаторов меньше, чем элементов массива, каждый элемент, не инициализированный явно, должен быть инициализирован нулем.
  • Во время инициализации значения , если T является (возможно, cv-квалифицированным) типом класса без объединения без конструктора, предоставленного пользователем, то объект инициализируется нулем.

Обнулить объект или ссылку типа T означает:

  • если T - скалярный тип (3.9), объекту присваивается значение 0 (ноль), принимаемое как интегральное постоянное выражение , преобразованное в T;
  • если T является (возможно, cv-квалифицированным) типом класса без объединения, каждый нестатический член данных и каждый подобъект базового класса инициализируется нулем, а заполнение инициализируется нулевыми битами;
  • если T является (возможно, cv-квалифицированным) типом объединения, первый нестатический именованный член данных объекта инициализируется нулем, а заполнение инициализируется нулевыми битами;
  • если T является типом массива, каждый элемент инициализируется нулем;
  • если T - ссылочный тип, инициализация не выполняется.

2. Тип инициализатора: 8.5.6 [dcl.init] _default-initialize_

Выполняется в следующих случаях

  • Если новый инициализатор опущен, объект инициализируется по умолчанию; если инициализация не выполняется, объект имеет неопределенное значение.
  • Если для объекта не указан инициализатор, объект инициализируется по умолчанию, за исключением объектов со статической продолжительностью хранения или продолжительностью хранения потока.
  • Когда базовый класс или нестатический член данных не упоминается в списке инициализаторов конструктора и вызывается этот конструктор.

Инициализация по умолчанию объекта типа T означает:

  • если T является (возможно, cv-квалифицированным) типом класса, отличным от POD (раздел 9), вызывается конструктор по умолчанию для T (и инициализация плохо сформирована, если T не имеет доступного конструктора по умолчанию);
  • если T является типом массива, каждый элемент инициализируется по умолчанию;
  • в противном случае инициализация не выполняется.

Примечание. До C ++ 11 только типы классов, не относящиеся к POD, с автоматической продолжительностью хранения считались инициализированными по умолчанию, если инициализатор не использовался.


3. Тип инициализатора: 8.5.7 [dcl.init] _value-initialize_

  1. Когда объект (безымянный временный, именованная переменная, длительность динамического хранения или нестатический член данных), инициализатором которого является пустой набор круглых скобок, то есть () или фигурные скобки {}

Инициализировать значение объекта типа T означает:

  • если T является (возможно, cv-квалифицированным) типом класса (пункт 9) с конструктором, предоставленным пользователем (12.1), то вызывается конструктор по умолчанию для T (и инициализация плохо сформирована, если T не имеет доступного конструктора по умолчанию) ;
  • если T является (возможно, cv-квалифицированным) типом класса без объединения без предоставленного пользователем конструктора, то каждый нестатический член данных и компонент базового класса T инициализируется значением; тогда объект инициализируется нулем, и, если неявно объявленный конструктор по умолчанию T нетривиален, этот конструктор вызывается.
  • если T является типом массива, то каждый элемент инициализируется значением;
  • в противном случае объект инициализируется нулем.

Итак, чтобы подвести итог

Примечание . Соответствующая цитата из стандарта выделена жирным шрифтом.

  • новый A: инициализируется по умолчанию (оставляет A :: m неинициализированным)
  • new A (): инициализировать нулём A, так как кандидат с инициализированным значением не имеет предоставленного пользователем или удаленного конструктора по умолчанию. если T является (возможно, cv-квалифицированным) типом класса без объединения без предоставленного пользователем конструктора, тогда объект инициализируется нулем, и, если неявно объявленный конструктор по умолчанию для T нетривиален, этот конструктор вызывается.
  • новый B: инициализируется по умолчанию (оставляет B :: m неинициализированным)
  • new B (): значение инициализирует B, который инициализирует нулем все поля; если T является (возможно, cv-квалифицированным) типом класса (раздел 9) с предоставленным пользователем конструктором (12.1), то вызывается конструктор по умолчанию для T
  • new C: инициализирует C по умолчанию, который вызывает ctor по умолчанию. если T является (возможно, cv-квалифицированным) типом класса (раздел 9), вызывается конструктор по умолчанию для T. Более того, если new-initializer опущен, объект инициализируется по умолчанию
  • new C (): инициализирует значение C, которое вызывает ctor по умолчанию. если T является типом класса (возможно, квалифицированным cv) (раздел 9) с предоставленным пользователем конструктором (12.1), то вызывается конструктор по умолчанию для T. Более того, объект, инициализатором которого является пустой набор круглых скобок, т. Е. (), Должен быть инициализирован значением.
Абхиджит
источник
0

Я могу подтвердить, что в C ++ 11 все, что упомянуто в вопросе в C ++ 14, является правильным, по крайней мере, в соответствии с реализациями компилятора.

Чтобы проверить это, я добавил в свой набор тестов следующий код . Я тестировал -std=c++11 -O3в GCC 7.4.0, GCC 5.4.0, Clang 10.0.1 и VS 2017, и все тесты ниже прошли успешно.

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

Места, где UB!упоминается, относятся к неопределенному поведению, а фактическое поведение, вероятно, будет зависеть от многих факторов ( a.mможет быть равно 42, 0 или какой-то другой мусор). Места, где ~UBупоминается, также являются неопределенным поведением в теории, но на практике, из-за использования нового размещения, очень маловероятно, a->mчто будет равно чему-либо другому, кроме 42.

Борис Дальштейн
источник