Member not zeroed, ошибка clang++?
Рассмотрим следующий код:
class A {
public:
int i;
A() {}
};
class B {
public:
A a;
int i;
};
int main() {
B* p = new B {};
std::cout << p->i << " " << p->a.i << "\n";
}
Скомпилированный с -std = С++ 11 в clang++, p->i
оказывается равным нулю, но p->a.i
этого не делает. Не следует ли обнулить весь объект до тех пор, пока его класс не имеет конструктора, предоставленного пользователем?
EDIT: Поскольку в комментариях есть несколько подробных обсуждений, я думаю, что лучше добавить некоторые выдержки из стандарта здесь:
Для инициализации значения объекта типа T
означает:
- Если
T
является (возможно, cv-qualit) типом класса (раздел 9) с предоставленным пользователем конструктором (12.1), тогда вызывается конструктор по умолчанию для T (и инициализация плохо сформирована, если T
не имеет доступного конструктора по умолчанию); - если
T
является (возможно, cv-квалифицированным) классом неединичного класса без конструктора, предоставленного пользователем, тогда объект инициализируется нулем и, если T
s неявно объявленный конструктор по умолчанию является нетривиальным, этот конструктор называется. - if
T
- тип массива, тогда каждый элемент инициализируется значением; - в противном случае объект инициализируется нулем.
Для нулевой инициализации объекта или ссылки типа T означает:
- если
T
- скалярный тип (3.9), объект устанавливается в значение 0
(ноль), взятое как интегральное постоянное выражение, преобразованное в T
; - если
T
является (возможно, cv-квалифицированным) классом неединичного класса, каждый нестатический элемент данных и каждый подобъект базового класса инициализируются нулями, а заполнение инициализируется нулевыми битами; - Если
T
является (возможно, cv-квалифицированным) типом объединения, объекты первого нестатического именованного элемента данных инициализируются нулем, а заполнение инициализируется нулевыми битами; - если
T
- тип массива, каждый элемент инициализируется нулем; - Если
T
является ссылочным типом, инициализация не выполняется.
Здесь применяется вторая пуля.
Ответы
Ответ 1
Clang правильный, по стандарту С++ 11 плюс соответствующие DR
В исходной спецификации С++ 11 B{}
выполнит инициализацию значения, в результате чего a.i
будет инициализироваться нулем. Это было изменение поведения по сравнению с С++ 98 для таких случаев, как
B b = {};
... которые обрабатывались как агрегатная инициализация в С++ 98, но рассматривались как инициализация значений в С++ 11 FDIS.
Однако поведение в этом случае было изменено с помощью основной проблемы 1301, которая восстановила поведение С++ 98, указав, что агрегатная инициализация используется всякий раз, когда агрегат инициализируется скобками-init-list. Поскольку эта проблема считается DR, она рассматривается как де-факто применительно к более ранним версиям стандарта С++, поэтому ожидается, что соответствующий компилятор С++ 11 будет выполнять агрегатную инициализацию, а не инициализацию значения.
В конечном счете, это плохая идея полагаться на инициализацию значений для инициализации ваших членов данных, особенно для класса, который имеет предоставленные пользователем конструкторы.
Ответ 2
Он действительно выглядит как ошибка (или, как указано в комментариях, ведет себя в соответствии с С++ 03, несмотря на указание С++ 11). В С++ 11 инициализация значения должна обнулять члены a
перед вызовом его конструктора по умолчанию. Инициализация B
определяется этим правилом 8.5/7
если T является (возможно, cv-квалифицированным) классом неединичного класса без конструктора, предоставленного пользователем, тогда объект инициализируется нулем и, если Ts неявно объявленный конструктор по умолчанию не является -виртуальный, этот конструктор называется.
Нулевая инициализация должна рекурсивно инициализировать нуль a
за это правило 8.5/5
если T является (возможно, cv-квалифицированным) классом неединичного класса, каждый нестатический элемент данных и каждый подобъект базового класса инициализируются нулем
и, конечно, нулевая инициализация a
должна установить i
в ноль.
Ответ 3
Это not ошибка компилятора , это ошибка в коде. Компилятор, похоже, реализует поведение С++ 03, но это сильно изменилось в С++ 11.
Это некоторые релевантные цитаты из стандартов С++ 03 и С++ 11
В С++ 03:
Для инициализации объекта типа типа T означает:
- если T - тип класса (раздел 9) с объявленным пользователем конструктором (12.1), то по умолчанию конструктор для T (и инициализация плохо сформирована, если T не имеет доступного конструктора по умолчанию);
- , если T - неединичный класс типа без конструктора, объявленного пользователем, то все нестатические данные компонент элемента и базового класса T инициализируется значением;
(акцент мой)
В С++ 11:
Для инициализации объекта типа типа T означает:
- если T является (возможно, cv-qualified) тип класса (раздел 9) с предоставленным пользователем конструктором (12.1), то конструктор по умолчанию для T называется (и инициализация плохо сформирована, если T не имеет доступного значения по умолчанию конструктор);
- если T является (возможно, cv-квалифицированным) классом типа non-union без созданного пользователем конструктора, тогда объект нулевое инициализированное и, если Ts неявно объявленный конструктор по умолчанию является нетривиальным, этот конструктор называется.
и
Для нулевой инициализации объекта или ссылки типа T означает:
- если T является скалярного типа (3.9), объект устанавливается в значение 0 (ноль), взятое как интегральное постоянное выражение, преобразованное в T;
- если T является (возможно, cv-квалифицированный) тип неединичного класса, нестатические данные член и каждый подобъект базового класса инициализируется нулем и дополнением инициализируется нулевыми битами;
Примечание. Следующее относится только к С++ 03:
Либо удалите A
созданный пользователем конструктор, либо измените его на
A() : i() {}
Когда вы стоите-инициализируете B
здесь,
B* p = new B {};
it value инициализирует свои члены данных. Поскольку A
имеет конструктор по умолчанию, инициализация значения приводит к вызову. Но этот конструктор явно не инициализирует A::i
, поэтому он инициализируется по умолчанию, что для int
означает, что инициализация не выполняется.
Если вы не предоставили конструктор по умолчанию для A
, тогда член данных получил бы нуль-инициализацию, когда A
инициализируется значением.
Ответ 4
Интегральные типы не должны быть инициализированы значением, подобным этому в конструкторе, отличном от стандартного (поскольку вы предоставили конструктор)
Измените свой конструктор на A() : i(0) {}
.