У меня есть шаблонный 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 снова за указание реальной проблемы.