Почему член в этом примере не инициализируется нулями? - PullRequest
0 голосов
/ 03 января 2019

Это особенно касается C ++ 11:

#include <iostream>
struct A {
    A(){}
    int i;
};
struct B : public A {
    int j;
};
int main() {
    B b = {};
    std::cout << b.i << b.j << std::endl;
}

Компиляция с g ++ 8.2.1:

$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:25:25: warning: ‘b.B::<anonymous>.A::i’ is used uninitialized in this function [-Wuninitialized]
     std::cout << b.i << " " << b.j << std::endl

gcc определяет b.i как неинициализированный, но я думаю,она должна инициализироваться нулями вместе с b.j.

То, что, по моему мнению, происходит (в частности, C ++ 11, из рабочего проекта ISO / IEC N3337, выделено мной):

  • B не является агрегатом, поскольку имеет базовый класс.Общедоступные базовые классы были разрешены только в агрегатах в C ++ 17.
  • A не является агрегатом, поскольку в нем есть предоставленный пользователем конструктор

Раздел 8.5.1

Агрегат - это массив или класс (раздел 9) с без предоставленных пользователем конструкторов (12.1), без инициалов-скобок или равных для нестатических элементов данных (9.2)), нет личных или защищенных нестатических элементов данных (пункт 11), нет базовых классов (пункт 10) и нет виртуальных функций (10.3).

  • b получает список, инициализированный пустым списком фигурных скобок

Раздел 8.5.4

Определена инициализация списка объекта или ссылки типа Tследующим образом:
- Если список инициализаторов не имеет элементов и T является типом класса с конструктором по умолчанию, объект инициализируется значением .
- В противном случае, если T является агрегатом,инициализация агрегата выполняется (8.5.1).

  • Это означает bполучает инициализированное значение
  • B имеет неявно определенный конструктор по умолчанию, поэтому b инициализация значения вызывает инициализацию с нуля
  • b.B::A инициализируется с нуля, что инициализирует с нуляb.B::A.i, а затем b.B::j инициализируется нулями.

Раздел 8.5

Инициализация объекта или ссылки типа T с нуля означает:
...
- если T является (возможно, cv-квалифицированный) тип класса, не являющийся объединением, каждый элемент нестатических данных и каждый подобъект базового класса инициализируются нулями , а заполнение инициализируется нулевыми битами;

...

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

Однако, похоже, что gccговоря, что только b.B::j будет инициализироваться нулем.Почему это?

Одна из причин, по которой я могу придумать, заключается в том, что B обрабатывается как совокупность, которая инициализирует b.B::A пустым списком.B, безусловно, не является агрегатом, потому что gcc справедливо выдает ошибку, если мы пытаемся использовать агрегатную инициализацию.

// ... as in the above example
int main() {
    B b = {A{}, 1};
    std::cout << b.i << " " << b.j << std::endl;
}

Компиляция с C ++ 11

$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:10:18: error: could not convert ‘{A(), 1}’ from ‘<brace-enclosed initializer list>’ to ‘B’
     B b = {A{}, 1};

Компиляция с C++ 17

g++ -std=c++17 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:11:25: warning: ‘b.B::<anonymous>.A::i’ is used uninitialized in this function [-Wuninitialized]
     std::cout << b.i << " " << b.j << std::endl;

И мы можем видеть, что b.i неинициализирован, потому что B является агрегатом, а b.B::A инициализируется выражением, которое само по себе оставляет A::i неинициализированным.

Так что это не совокупность.Другая причина в том, что b.B::j инициализируется нулями, а b.B::A инициализируется значениями, но я не вижу этого нигде в спецификации.

Последняя причина, если более старая версиястандарт был вызван.Из cppreference :

2) если T является типом класса, не являющимся объединением, без каких-либо пользовательских конструкторов, каждый нестатический член данных и компонент базового класса Tзначение инициализирован;(до C ++ 11)

В этом случае оба значения b.B::i и b.B::A будут инициализированы значением, что приведет к такому поведению, но это помечено как "(до C ++ 11) ".

Ответы [ 3 ]

0 голосов
/ 03 января 2019

Для любого класса, если существует один определяемый пользователем конструктор, он должен использоваться, и A(){} не инициализирует i.

0 голосов
/ 07 января 2019

Я бы тоже пошел с ошибкой компилятора.

  • Я думаю, мы все можем согласиться с тем, что b получает инициализированное значение (8.5.4)
  • Использование

    значение-инициализация объекта типа T означает:
    - если T является (возможно, cv-квалифицированным) типом класса без объединения без предоставленного пользователем конструктора, тогда объект инициализируется нулями и , если неявно объявленный по умолчанию конструктор T не является тривиальным, этот конструктор вызывается.

    Так что должно произойти, это сначала инициализация нуля, , затем ctor по умолчанию можно назвать

  • И с определением:

    Для инициализации нуля объекта или ссылки типаT означает:
    - если T является (возможно, cv-квалифицированным) типом класса, не являющимся объединением, каждый нестатический член данных и каждый подобъект базового класса инициализируются нулями , а заполнение инициализируется какноль битов;

Следовательно, должно произойти следующее:

  1. Fillsizeof(B) с нулями
  2. Вызов конструктора подобъекта A, который ничего не делает.

Я предполагаю, что это ошибка в оптимизации.Сравните вывод -O0 с -O1: https://godbolt.org/z/20QBoR. Без оптимизации поведение будет правильным.Clang, с другой стороны, является правильным в обоих случаях: https://godbolt.org/z/7uhlIi

Эта «ошибка» все еще присутствует с более новыми стандартными флагами в GCC: https://godbolt.org/z/ivkE5K

Однако я предполагаю, что в C ++ 20B является «агрегатом», поэтому поведение становится стандартным.

0 голосов
/ 03 января 2019

Ничего не инициализируется i.Это не происходит автоматически.Вам нужно либо инициализировать его в классе, либо в списке инициализации конструктора класса.Или удалите ваш нетривиальный / определяемый пользователем конструктор (или = default it, что делает его тривиальным).

Компилятор использует предоставленный вами конструктор, и этот ctor выполняет not initializei.

...