Инициализация значения: инициализация по умолчанию или инициализация нуля?
У меня есть шаблонный gray_code
класс, предназначенный для хранения некоторого целого числа без знака, базовые биты которого хранятся в коде кода Серы. Вот он:
template<typename UnsignedInt>
struct gray_code
{
static_assert(std::is_unsigned<UnsignedInt>::value,
"gray code only supports built-in unsigned integers");
// Variable containing the gray code
UnsignedInt value;
// Default constructor
constexpr gray_code()
= default;
// Construction from UnsignedInt
constexpr explicit gray_code(UnsignedInt value):
value( (value >> 1) ^ value )
{}
// Other methods...
};
В каком-то общем алгоритме я написал что-то вроде этого:
template<typename UnsignedInt>
void foo( /* ... */ )
{
gray_code<UnsignedInt> bar{};
// Other stuff...
}
В этом фрагменте кода я ожидал, что bar
будет иметь нулевую инициализацию, и поэтому bar.value
будет нулевой инициализирован. Однако, после того, как вы столкнулись с неожиданными ошибками, кажется, что bar.value
инициализируется мусором (точнее, 4606858) вместо 0u
. Это меня удивило, поэтому я пошел на cppreference.com, чтобы посмотреть, что именно должна была делать линия выше...
Из того, что я могу прочитать, форма T object{};
соответствует инициализации значений. Я нашел эту цитату интересной:
Во всех случаях, если используется пустая пара фигурных скобок {}, а T - совокупный тип, вместо инициализации значения выполняется инициализация агрегата.
Однако gray_code
имеет предоставленный пользователем конструктор. Поэтому он не является агрегатом, поэтому агрегатная инициализация не выполняется. gray_code
не имеет конструктора с std::initializer_list
, поэтому инициализация списка также не выполняется. Инициализированное значение gray_code
должно следовать обычным правилам инициализации значения С++ 14:
1) Если T - тип класса без конструктора по умолчанию или с предоставленным пользователем конструктором по умолчанию или с удаленным конструктором по умолчанию, объект инициализируется по умолчанию.
2) Если T - тип класса без предоставленного пользователем или удаленного конструктора по умолчанию (то есть, это может быть класс с дефолтным конструктором по умолчанию или с неявным образом), тогда объект инициализируется нулем и то он инициализируется по умолчанию, если он имеет нетривиальный конструктор по умолчанию.
3) Если T - тип массива, каждый элемент массива инициализируется значением.
4) В противном случае объект инициализируется нулем.
Если я правильно прочитал, gray_code
имеет явно установленный по умолчанию (не предоставленный пользователем) конструктор по умолчанию, поэтому 1) не применяется. Он имеет стандартный конструктор по умолчанию, поэтому 2) применяется: gray_code
is нулевой инициализации. По умолчанию конструктор по умолчанию, похоже, отвечает всем требованиям тривиального конструктора по умолчанию, поэтому инициализация по умолчанию не должна выполняться. Давайте посмотрим, как gray_code
инициализируется нулем:
-
Если T - скалярный тип, начальное значение объекта - это интегральная константа, неявно преобразованная в T.
-
Если T - тип неединичного класса, все базовые классы и нестатические элементы данных инициализируются нулем, а все дополнения дополняются нулевыми битами. Конструкторы, если они есть, игнорируются.
-
Если T является типом объединения, первый нестатический именованный элемент данных инициализируется нулем, и все заполнение инициализируется нулевыми битами.
-
Если T - тип массива, каждый элемент инициализируется нулем
-
Если T является ссылочным типом, ничего не делается.
gray_code
- тип неединичного класса. Поэтому все его нестатические элементы данных должны быть инициализированы, что означает, что value
инициализируется нулем. value
удовлетворяет std::is_unsigned
и поэтому является скалярным типом, что означает, что он должен быть инициализирован "интегральной константой нуля, неявно преобразованной в T".
Итак, если я правильно прочитал все это, в функции foo
выше, bar.value
всегда должен быть инициализирован с помощью 0
, и он никогда не должен инициализироваться мусором, я прав?
Примечание. компилятор, скомпилированный с моим кодом, - это MinGW_w4 GCC 4.9.1 с (потоки POSIX и карликовые исключения) в случае, если это помогает. Хотя иногда я получаю мусор на своем компьютере, я никогда не получал ничего, кроме нуля, с онлайн-компиляторами.
Обновление: Кажется, что является ошибкой GCC, что моя ошибка, а не моя компилятор. Собственно, при написании этого вопроса я предположил для простоты, что
class foo {
foo() = default;
};
и
class foo {
foo();
};
foo::foo() = default;
были эквивалентными. Они не. Вот цитата из стандарта С++ 14, раздел [dcl.fct.def.default]:
Функция предоставляется пользователем, если она объявлена пользователем и явно не установлена по умолчанию или удалено в его первом объявлении.
Другими словами, когда я получил значения мусора, мой дефолтный конструктор по умолчанию был действительно предоставлен пользователем, поскольку он не был явно искажен в его первом объявлении. Поэтому произошло не инициализация нуля, а инициализация по умолчанию. Спасибо @Columbo снова за указание реальной проблемы.
Ответы
Ответ 1
Итак, если я правильно прочитал все это, в функции foo
выше, bar.value
всегда следует инициализировать с помощью 0
, и он никогда не должен быть инициализированный мусором, я прав?
Да. Ваш объект инициализируется прямым списком. С++ 14's * [dcl.init.list]/3 указывает, что
Определяется список-инициализация объекта или ссылки типа T
следующим образом:
-
[... Неприменимые маркеры...]
-
В противном случае, если T
является агрегатом, выполняется агрегатная инициализация (8.5.1).
-
В противном случае, если в списке инициализаторов нет элементов, а T - тип класса с конструктором по умолчанию, объект инициализируется значением.
-
[...]
Ваш класс не является агрегатом, поскольку он имеет предоставленные пользователем конструкторы, но у него есть конструктор по умолчанию. [Dcl.init]/7:
Для инициализации значения объекта типа T
означает:
-
если T
является (возможно, cv-квалифицированным) типом класса (раздел 9) без конструктора по умолчанию (12.1) или конструктора по умолчанию, который предоставляется или удаляется пользователем, тогда объект инициализируется по умолчанию;
-
если T является (возможно, cv-квалифицированным) классом типа без предоставленного пользователем или удаленного конструктора по умолчанию, тогда объект нулевой инициализации и семантические ограничения для проверяется инициализация по умолчанию, а если T
имеет нетривиальный конструктор по умолчанию, объект инициализируется по умолчанию;
[dcl.fct.def.default]/4:
Специальная функция-член предоставляется пользователю, если она объявлена пользователем и явно не дефолт [...] в его первом объявлении.
Таким образом, ваш конструктор не предоставляется пользователям, поэтому объект инициализируется нулем. (Конструктор не вызывается, так как его тривиально)
И, наконец, если это неясно, ноль-инициализировать объект или ссылку типа T
означает:
-
если T
является скалярным типом (3.9), объект инициализируется значением, полученным преобразованием целочисленного литерала 0
(ноль) в T
;
-
если T
является (возможно, cv-квалифицированным) классом неединичного класса, каждый нестатический элемент данных и каждый подобъект базового класса инициализируются нулем, а заполнение инициализируется нулевыми битами;
-
[...]
Таким образом, либо
* Ответ по-прежнему да в С++ 11, хотя цитируемые разделы не эквивалентны.