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

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

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

Краткое описание примеров:

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

  • В C ++ 1998 существует 2 типа инициализации: ноль- и инициализация по умолчанию.
  • В C ++ 2003 был добавлен третий тип инициализации, инициализация значения.
  • В 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 - неопределенное значение (A - POD)
  • 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 - неопределенное значение (A - POD)
  • new A() - инициализация значения A, что является нулевой инициализацией, поскольку это POD.
  • new B - инициализируется по умолчанию (оставляет B::m неинициализированным, B не-POD)
  • new B() - инициализирует значение B, которое инициализирует нулем все поля, поскольку его ctor по умолчанию генерируется компилятором, а не определенным пользователем.
  • new C - инициализирует по умолчанию C, который вызывает ctor по умолчанию. (C::m инициализирован нулем, C не является POD)
  • new C() - инициализирует значение 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 неинициализированным) (A - POD)

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

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

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

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

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

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

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

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

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

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

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


person Gabriel    schedule 21.04.2015    source источник
comment
здесь есть хорошее объяснение: en.cppreference.com/w/cpp/language/ default_constructor   -  person Richard Hodges    schedule 21.04.2015
comment
Насколько я могу судить, в этих примерах разница только между C ++ 98 и C ++ 03. Проблема, кажется, описана в N1161 (есть более поздние версии этого документа) и CWG DR № 178. формулировку необходимо было изменить в C ++ 11 из-за новых функций и новой спецификации POD, и она снова изменилась в C ++ 14 из-за дефектов в формулировке C ++ 11, но эффекты в этих случаях не меняются.   -  person dyp    schedule 21.04.2015
comment
Хотя это и скучно, struct D { D() {}; int m; };, возможно, стоит включить в ваш список.   -  person Yakk - Adam Nevraumont    schedule 21.04.2015
comment
Есть хороший тревожный плакат, который раскрывает этот беспорядок: randomcat.org/cpp_initialization/initialization.png   -  person Gabriel    schedule 15.03.2021


Ответы (3)


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

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

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

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

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

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

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

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

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

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

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

    • if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized;
    • если T является (возможно, cv-квалифицированным) типом класса без предоставленного пользователем или удаленного конструктора по умолчанию, то объект инициализируется нулем и проверяются семантические ограничения для инициализации по умолчанию, и если T имеет не- тривиальный конструктор по умолчанию, объект инициализируется по умолчанию;
    • если T является типом массива, то каждый элемент инициализируется значением;
    • в противном случае объект инициализируется нулем.

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

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


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

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

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

Следующий ответ расширяет ответ 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 может быть инициализирована как скобка-или-равный-инициализатор, который может быть как braced-init-list или предложение-инициализатора, совокупно именуемое фигурной-скобкой-или-равным-инициализатором, или с использованием (список-выражений). До C ++ 11 поддерживалось только (список-выражений) или предложение-инициализатора, хотя предложение -инициализатора было более ограниченным, чем то, что мы есть в C ++ 11. В C ++ 11 initializer-clause теперь поддерживает braced-init-list помимо assignment-expression, как это было в C ++ 03. Следующая грамматика резюмирует новое поддерживаемое предложение, в котором часть, выделенная жирным шрифтом, была недавно добавлена ​​в стандарт C ++ 11.

инициализатор:
& nbsp & nbsp & nbsp & nbsp инициализатор-скобка-или-равный
& nbsp & nbsp & nbsp & nbsp (список-выражений)
инициализатор-скобки-или-равно:
& nbsp & nbsp & nbsp & nbsp = предложение-инициализатора
& nbsp & nbsp & nbsp & nbsp список-инициализаторов в фигурных скобках
предложение-инициализатора:
& nbsp & nbsp & nbsp & nbspassignment-expression
& nbsp & nbsp & nbspbs -init-list
список-инициализаторов:
& nbsp & nbsp & nbsp & nbsp предложение-инициализатора ... opt
& nbsp & nbsp & nbsp & nbsp список-инициализаторов, предложение-инициализатора ... opt **
braced-init-list:
& nbsp & nbsp & nbsp & nbsp {список-инициализаторов, opt} < br /> & nbsp & nbsp & nbsp & nbsp {}

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

Как и 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-квалифицированным) типом класса без объединения, каждый нестатический член данных и каждый подобъект базового класса инициализируется нулем < em>, а заполнение инициализируется нулевыми битами;
  • если 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 C (): инициализирует значение C, которое вызывает ctor по умолчанию. если T является (возможно, cv-квалифицированным) типом класса (раздел 9) с предоставленным пользователем конструктором (12.1), то вызывается конструктор по умолчанию для T. Более того. , Объект, инициализатором которого является пустой набор круглых скобок, т. е. (), должен быть инициализирован значением
person Abhijit    schedule 21.04.2015

Я могу подтвердить, что в 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, также являются неопределенным поведением в теории, но на практике, из-за использования размещения new, очень маловероятно, что a->m будет равно какому-либо другому, кроме 42.

person Boris Dalstein    schedule 19.02.2020