Default-initialization
Это инициализация, выполняемая при создании объекта без инициализатора.
Содержание |
Синтаксис
T object
;
|
(1) | ||||||||
new
T
|
(2) | ||||||||
Объяснение
Инициализация по умолчанию выполняется в трёх ситуациях:
Эффекты инициализации по умолчанию:
-
если
Tявляется (возможно, cv-квалифицированным) не-POD (до C++11) классом, конструкторы рассматриваются и проходят разрешение перегрузки для пустого списка аргументов. Выбранный конструктор (который является одним из конструкторов по умолчанию ) вызывается для предоставления начального значения нового объекта; -
если
Tявляется массивом, каждый элемент массива инициализируется по умолчанию; - в противном случае инициализация не выполняется (см. примечания ).
|
Только (возможно, cv-квалифицированные) не-POD классовые типы (или их массивы) с автоматической продолжительностью хранения считались инициализированными по умолчанию, когда инициализатор не используется. Скалярные типы и POD типы с динамической продолжительностью хранения считались неинициализированными (начиная с C++11, эта ситуация была переклассифицирована как форма инициализации по умолчанию). |
(until C++11) |
|
(до C++11) |
|
(начиная с C++11) |
каждый
потенциально конструируемый
базовый класс
T
является конструируемым по умолчанию как константный.
Неопределённые и ошибочные значения
|
При получении памяти для объекта с автоматической или динамической продолжительностью хранения, объект имеет неопределённое значение . Если для объекта не выполняется инициализация, этот объект сохраняет неопределённое значение до тех пор, пока это значение не будет заменено. |
(до C++26) |
|
При получении памяти для объекта с автоматической или динамической продолжительностью хранения, байты, составляющие память для объекта, имеют следующие начальные значения:
Если для объекта (включая подобъекты ) не выполняется инициализация, такой байт сохраняет своё начальное значение до тех пор, пока это значение не будет заменено.
|
(начиная с C++26) |
Если вычисление производит неопределённое значение, поведение является неопределённым .
|
Если вычисление приводит к ошибочному значению, поведение является ошибочным . |
(since C++26) |
Особые случаи
Следующие типы являются неинициализированно-дружественными :
| (начиная с C++17) |
- unsigned char
- char , если его базовый тип является unsigned char
Для неопределённого или ошибочного (since C++26) значения value , неинициализированное результирующее значение для value равно:
- Неопределённое значение, если value также является неопределённым значением.
|
(начиная с C++26) |
Если вычисление eval производит неопределённое или ошибочное (since C++26) значение value типа, дружественного к неинициализированным данным, поведение является определённым в следующих случаях:
- eval представляет собой вычисление одного из следующих выражений и операндов:
-
- Второй или третий операнд условного выражения .
- Правый операнд выражения с запятой .
-
Операнд
целочисленного преобразования
,
явного приведения
или
static_castк дружественному к неинициализированным значениям типу. - Выражение с отбрасываемым значением .
- В этом случае результатом операции является неинициализированное значение результата value .
- eval представляет собой вычисление правого операнда простого оператора присваивания , левый операнд которого является lvalue типа, дружественного к неинициализированным объектам.
- В данном случае значение объекта, на который ссылается левый операнд, заменяется неинициализированным результирующим значением value .
- eval представляет собой вычисление выражения инициализации при инициализации объекта типа, дружественного к неинициализированному состоянию.
| (начиная с C++17) |
- В данном случае этот объект инициализируется неинициализированным значением результата value .
Преобразование неопределенного значения типа, допускающего неинициализированное состояние, дает неопределенное значение.
|
Преобразование ошибочного значения типа, дружественного к неинициализированным значениям, дает ошибочное значение; результатом преобразования является значение преобразуемого операнда. |
(since C++26) |
// Случай 1: Неинициализированные объекты с динамической продолжительностью хранения // Все версии C++: неопределённое значение + неопределённое поведение int f(bool b) { unsigned char* c = new unsigned char; unsigned char d = *c; // OK, "d" имеет неопределённое значение int e = d; // неопределённое поведение return b ? d : 0; // неопределённое поведение, если "b" истинно } // Случай 2: Неинициализированные объекты с автоматической продолжительностью хранения // до C++26: неопределённое значение + неопределённое поведение // с C++26: ошибочное значение + ошибочное поведение int g(bool b) { unsigned char c; // "c" имеет неопределённое/ошибочное значение unsigned char d = c; // нет неопределённого/ошибочного поведения, // но "d" имеет неопределённое/ошибочное значение assert(c == d); // выполняется, но оба целочисленных продвижения имеют // неопределённое/ошибочное поведение int e = d; // неопределённое/ошибочное поведение return b ? d : 0; // неопределённое/ошибочное поведение, если "b" истинно } // Аналогично случаю 2 void h() { int d1, d2; // "d1" и "d2" имеют неопределённые/ошибочные значения int e1 = d1; // неопределённое/ошибочное поведение int e2 = d1; // неопределённое/ошибочное поведение assert(e1 == e2); // выполняется assert(e1 == d1); // выполняется, неопределённое/ошибочное поведение assert(e2 == d1); // выполняется, неопределённое/ошибочное поведение // нет неопределённого/ошибочного поведения, // но "d2" имеет неопределённое/ошибочное значение std::memcpy(&d2, &d1, sizeof(int)); assert(e1 == d2); // выполняется, неопределённое/ошибочное поведение assert(e2 == d2); // выполняется, неопределённое/ошибочное поведение }
Примечания
Ссылки и константные скалярные объекты не могут быть инициализированы по умолчанию.
| Макрос тестирования возможностей | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_constexpr
|
201907L
|
(C++20) | Тривиальная инициализация по умолчанию и asm-декларация в constexpr функциях |
Пример
#include <string> struct T1 { int mem; }; struct T2 { int mem; T2() {} // "mem" отсутствует в списке инициализации }; int n; // статическая не-классовая переменная, выполняется двухфазная инициализация: // 1) инициализация нулями устанавливает n в ноль // 2) инициализация по умолчанию ничего не делает, оставляя n равным нулю int main() { [[maybe_unused]] int n; // не-классовая переменная, значение неопределено std::string s; // класс, вызывает конструктор по умолчанию, значение равно "" std::string a[2]; // массив, инициализирует элементы по умолчанию, значение равно {"", ""} // int& r; // Ошибка: ссылка // const int n; // Ошибка: константная не-классовая переменная // const T1 t1; // Ошибка: константный класс с неявным конструктором по умолчанию [[maybe_unused]] T1 t1; // класс, вызывает неявный конструктор по умолчанию const T2 t2; // константный класс, вызывает предоставленный пользователем конструктор по умолчанию // t2.mem инициализируется по умолчанию }
Отчеты о дефектах
Следующие отчеты о дефектах, изменяющих поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение в опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 178 | C++98 |
не было инициализации значением;
пустой инициализатор вызывал инициализацию по умолчанию (хотя new T ( ) также выполняет обнуляющую инициализацию) |
пустой инициализатор вызывает
инициализацию значением |
| CWG 253 | C++98 |
инициализация по умолчанию константного объекта не могла
вызывать неявно объявленный конструктор по умолчанию |
разрешено, если все подобъекты инициализированы |
| CWG 616 | C++98 |
преобразование lvalue в rvalue любого
неинициализированного объекта всегда было UB |
неопределённый unsigned char разрешён |
| CWG 1787 | C++98 |
чтение неопределённого
unsigned
char
кэшированного в регистре было UB |
сделано корректно определённым |