This is specifically regarding 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;
}
Compiling with 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 is detecting b.i
as uninitialized, but I would think it should be getting zero-initialized along with b.j
.
What I believe is happening (C++11 specifically, from the ISO/IEC working draft N3337, emphasis mine):
B
is not an aggregate because it has a base class. Public base classes were only allowed in aggregates in C++17.A
is not an aggregate because it has a user-provided constructor
Section 8.5.1
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
b
is getting list initialized with an empty braced-init-list
Section 8.5.4
List-initialization of an object or reference of type T is defined as follows:
— If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
— Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1).
- This means
b
gets value-initialized B
has an implicitly-defined default constructor, sob
value-initialization invokes zero-initializationb.B::A
gets zero-initialized, which zero-initalizesb.B::A.i
, and thenb.B::j
gets zero-initialized.
Section 8.5
To zero-initialize an object or reference of type T means:
...
— if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
...
To value-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
— if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.
However, it looks like gcc is saying only b.B::j
is going to get zero-initialized. Why is this?
One reason I can think of is if B
is being treated as an aggregate, which would initialize b.B::A
with an empty list.
B
is certainly not an aggregate, though, because gcc rightly errors if we try to use aggregate initialization.
// ... as in the above example
int main() {
B b = {A{}, 1};
std::cout << b.i << " " << b.j << std::endl;
}
Compiling with 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};
Compiling with 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;
And we can see that b.i
is uninitialized because B
is an aggregate, and b.B::A
is getting initialized by an expression that itself leaves A::i
uninitialized.
So it's not an aggregate. Another reason is if b.B::j
is getting zero-initialized, and b.B::A
is getting value-initialized, but I don't see that anywhere in the specs.
The last reason is if an older version of the standard was getting invoked. From cppreference:
2) if T is a non-union class type without any user-provided constructors, every non-static data member and base-class component of T is value-initialized; (until C++11)
In this case, both b.B::i
and b.B::A
would be value-initialized, which would cause this behavior, but that is marked as "(until C++11)".
A() = default;
Or just don't define it at all since you don't have any user-defined types members. – Heathenishb
is getting zero-initialized. This should zero-initialize all bases and non-static members. Unless it's not getting zero-initialized... – AssurbanipalA(){}
or change it toA() = default;
– Heathenish