Это особенно касается С++ 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 определяет bi
как неинициализированный, но я думаю, что он должен инициализироваться нулем вместе с bj
.
То, что я считаю, происходит (в частности, С++ 11, из рабочего проекта ISO/IEC N3337, выделено мной):
-
B
не является агрегатом, поскольку имеет базовый класс. Публичные базовые классы были разрешены только в агрегатах в С++ 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
вызывает инициализацию с нуля -
bB::A
инициализируется нулями, что инициализирует нулямиbB::Ai
, а затемbB::j
инициализируется нулями.
Раздел 8.5
Инициализация с нуля объекта или ссылки типа T означает:
...
- если T является (возможно, cv-квалифицированным) типом несоединения классов, каждый элемент нестатических данных и каждый подобъект базового класса инициализируются нулями, а заполнение инициализируется нулевыми битами;
...
Инициализировать значение объекта типа T означает:
- если T является (возможно, cv-квалифицированным) типом класса (раздел 9) с предоставленным пользователем конструктором (12.1), то вызывается конструктор по умолчанию для T (и инициализация является некорректной, если у T нет доступного конструктора по умолчанию) );
- если T является (возможно, cv-квалифицированным) типом класса, не являющимся объединением, без предоставленного пользователем конструктора, то объект инициализируется нулями и, если неявно объявленный Ts конструктор по умолчанию является нетривиальным, вызывается этот конструктор.
Тем не менее, похоже, что gcc говорит, что только bB::j
будет инициализироваться нулями. Почему это?
Одна причина, о которой я могу подумать, заключается в том, что B
обрабатывается как агрегат, который инициализирует bB::A
пустым списком. B
, конечно, не является агрегатом, потому что gcc справедливо ошибается, если мы пытаемся использовать агрегатную инициализацию.
// ... as in the above example
int main() {
B b = {A{}, 1};
std::cout << b.i << " " << b.j << std::endl;
}
Компиляция с С++ 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};
Компиляция с С++ 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;
И мы можем видеть, что bi
неинициализирован, потому что B
является агрегатом, а bB::A
инициализируется выражением, которое само оставляет A::i
неинициализированным.
Так что это не совокупность. Другая причина в том, что bB::j
инициализируется нулем, а bB::A
инициализируется значением, но я не вижу этого нигде в спецификации.
Последняя причина в том, что была вызвана более старая версия стандарта. Из контекста:
2) если T является типом класса, не являющимся объединением, без каких-либо предоставленных пользователем конструкторов, каждый элемент нестатических данных и компонент базового класса T инициализируется значением; (до С++ 11)
В этом случае и bB::i
и bB::A
будут инициализированы значением, что приведет к такому поведению, но будет помечено как "(до С++ 11)".