Всегда ли конструктор по умолчанию инициализирует все элементы?
Я могу поклясться, что не помню, что видел это раньше, и мне трудно поверить своим глазам:
Инициализирует ли неявно определенный конструктор по умолчанию для неагрегированного класса свои члены или нет?
В Visual C++, когда я запускаю этот невинно выглядящий код...
#include <string>
struct S { int a; std::string b; };
int main() { return S().a; }
... к моему удивлению, он возвращает ненулевое значение! Но если я уберу поле b
, то он возвращает ноль.
Я пробовал это на всех версиях VC++, я могу получить в свои руки, и, похоже, делает это на всех из них.
Но когда я пробую это на Clang и GCC, значения инициализируются нулями, независимо от того, пробую ли я это в режиме C++98 или C++11.
Какое правильное поведение? Разве это не гарантировано быть нулем?
2 ответа
Цитирую C++11:
5.2.3 Явное преобразование типов (функциональная запись) [expr.type.conv]
2 Выражение
T()
, гдеT
является спецификатором простого типа или спецификатором типа для полного типа объекта, не являющегося массивом, или (возможно, cv-квалифицированного)void
type, создает значение указанного типа, которое инициализируется значением (8.5; инициализация дляvoid()
дело). [...]8.5 Инициализаторы [dcl.init]
7 Инициализировать значение объекта типа
T
средства:
- ...
- если
T
является (возможно, квалифицированным по cv) типом класса без объединения без предоставленного пользователем конструктора, тогда объект инициализируется нулями и, еслиT
Неявно объявленный конструктор по умолчанию нетривиален, этот конструктор вызывается.- ...
Так что в C++11 S().a
должно быть равно нулю: объект инициализируется нулями до вызова конструктора, и конструктор никогда не изменяет значение a
к чему-либо еще.
До C++11 инициализация значения имела другое описание. Цитата N1577 (примерно C++03):
Инициализировать значение объекта типа T означает:
- ...
- если
T
это тип класса без объединения без объявленного пользователем конструктора, тогда каждый нестатический член данных и компонент базового классаT
инициализируется значением;- ...
- в противном случае объект инициализируется нулями
Здесь значение инициализации S
не вызывал конструктор, но вызывал инициализацию значения его a
а также b
члены. Значение инициализации этого a
Таким образом, member вызвал нулевую инициализацию этого конкретного члена. В C++ 03 результат также гарантированно равен нулю.
Еще раньше, переходя к самому первому стандарту, C++98:
Выражение
T()
, гдеT
является спецификатором простого типа (7.1.5.2) для завершенного типа объекта, не являющегося массивом, или (возможно, cv-квалифицированного)void
type, создает значение r указанного типа, значение которого определяется default-initialization (8.5; инициализация дляvoid()
дело).
По умолчанию инициализировать объект типа
T
средства:
- если
T
это тип класса не POD (раздел 9), конструктор по умолчанию дляT
называется (и инициализация плохо сформирована, еслиT
не имеет доступного конструктора по умолчанию);- ...
- в противном случае хранилище для объекта инициализируется нулями.
Поэтому, основываясь на этом самом первом стандарте, VC++ верен: когда вы добавляете std::string
член, S
становится не-POD-типом, а не-POD-типы не получают нулевую инициализацию, им просто вызывается их конструктор. Неявно созданный конструктор по умолчанию для S
не инициализирует a
член.
Таким образом, можно сказать, что все компиляторы верны, просто следуя различным версиям стандарта.
Как сообщает @Columbo в комментариях, более поздние версии VC++ вызывают a
инициализируемый элемент в соответствии с более поздними версиями стандарта C++.
(Все цитаты в первом разделе взяты из N3337, C++11 FD с редакционными изменениями)
Я не могу воспроизвести поведение с VC++ на rextester. Предположительно, ошибка (см. Ниже) уже исправлена в используемой ими версии, но не в вашей - @Drop сообщает, что последний выпуск, VS 2013 Update 4, не соответствует утверждению - в то время как предварительный просмотр VS 2015 передает их.
Просто чтобы избежать недоразумений: S
это действительно совокупность. [Dcl.init.aggr]/1:
Агрегат - это массив или класс (раздел 9) без предоставленных пользователем конструкторов (12.1), без закрытых или защищенных нестатических элементов данных (пункт 11), без базовых классов (пункт 10) и без виртуальных функций (10.3).
Это не имеет значения, хотя.
Семантика инициализации значения важна. [Dcl.init]/11:
Объект, инициализатором которого является пустой набор скобок, т. Е.
()
, должен быть инициализирован значением.
[Dcl.init]/8:
Инициализировать значение объекта типа
T
средства:
- если
T
является (возможно, cv-квалифицированным) типом класса (раздел 9) без конструктора по умолчанию (12.1) или конструктора по умолчанию, предоставленного или удаленного пользователем, тогда объект инициализируется по умолчанию;- если
T
является (возможно, cv-квалифицированным) типом класса без предоставленного пользователем или удаленного конструктора по умолчанию, тогда объект инициализируется нулями и проверяются семантические ограничения для инициализации по умолчанию, и еслиT
имеет нетривиальный конструктор по умолчанию, объект инициализируется по умолчанию;- [..]
Очевидно, что это справедливо независимо от того, b
в S
или нет. Так по крайней мере в C++ 11 в обоих случаях a
должно быть ноль. Clang и GCC показывают правильное поведение.
А теперь давайте посмотрим на C++03 FD:
Инициализировать значение объекта типа
T
средства:
- если
T
тип класса (раздел 9) с конструктором, объявленным пользователем (12.1) [..]- если
T
это тип класса без объединения без объявленного пользователем конструктора, тогда каждый нестатический член данных и компонент базового классаT
инициализируется значением;- если
T
тип массива, тогда каждый элемент инициализируется значением;- в противном случае объект инициализируется нулями
То есть даже в C++03 (где приведенная выше цитата в [dcl.init]/11 также существует в /7), a
должно быть 0
в обоих случаях.
Опять же, и GCC, и Clang верны с -std= C++03.
Как показано в ответе hvd, ваша версия совместима только с C++98 и C++98.