Инициализация значения: инициализация по умолчанию или инициализация нуля?

У меня есть шаблонный 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-квалифицированным) классом неединичного класса, каждый нестатический элемент данных и каждый подобъект базового класса инициализируются нулем, а заполнение инициализируется нулевыми битами;

  • [...]


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

  • Ваш компилятор прослушивается

  • ... или ваш код запускает поведение undefined в некоторой другой точке.


* Ответ по-прежнему да в С++ 11, хотя цитируемые разделы не эквивалентны.