Consider the following code:
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";
}
Compiled with -std=c++11 in clang++, p->i
turns out to be zero, but p->a.i
doesn't. Shouldn't the whole object be zeroed as long as its class doesn't have user-provided constructor?
EDIT: Since there are some extensive discussion in the comments, I think it's better to add some excerpt from the standard here:
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 ifT
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, ifT
’s implicitly-declared default constructor is non-trivial, that constructor is called.- if
T
is an array type, then each element is value-initialized;- otherwise, the object is zero-initialized.
To zero-initialize an object or reference of type T means:
- if
T
is a scalar type (3.9), the object is set to the value0
(zero), taken as an integral constant expression, converted toT
;- 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;- if
T
is a (possibly cv-qualified) union type, the object’s first non-static named data member is zero-initialized and padding is initialized to zero bits;- if
T
is an array type, each element is zero-initialized;- if
T
is a reference type, no initialization is performed.
The second bullet of each applies here.
Declaration tells the compiler about the existence of an entity in the program and its location. When you declare a variable, you should also initialize it. Initialization is the process of assigning a value to the Variable. Every programming language has its own method of initializing the variable.
The C++11 Standard Library was also revamped with new algorithms, new container classes, atomic operations, type traits, regular expressions, new smart pointers, async() facility, and of course a multithreading library.
There are two ways to initialize the variable. One is static initialization in which the variable is assigned a value in the program and another is dynamic initialization in which the variables is assigned a value at the run time.
Initialization gives a variable an initial value at the point when it is created. Assignment gives a variable a value at some point after the variable is created.
Clang is correct, per the C++11 standard plus relevant DRs
In the original C++11 specification, B{}
would perform value-initialization, resulting in a.i
being zero-initialized. This was a change in behavior compared to C++98 for cases like
B b = {};
... which were handled as aggregate initialization in C++98 but treated as value-initialization in C++11 FDIS.
However, the behavior in this case was changed by core issue 1301, which restored the C++98 behavior by mandating that aggregate initialization is used whenever an aggregate is initialized by a braced-init-list. Since this issue is considered a DR, it is treated as de facto applying to earlier revisions of the C++ standard, so a conforming C++11 compiler would be expected to perform aggregate initialization here rather than value-initialization.
Ultimately, it's a bad idea to rely on value-initialization to initialize your data members, especially for a class that has user-provided constructors.
It does indeed look like a bug (or, as pointed out in the comments, behaving according to C++03 despite specifying C++11). In C++11, value-initialisation should zero the members of a
before calling its default constructor. Initialisation of B
is governed by this rule of 8.5/7
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.
The zero-initialisation should recursively zero-initialise a
per this rule of 8.5/5
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, of course, zero-initialisation of a
should set i
to zero.
It is not a compiler bug, it is a bug in your code. The compiler seems to be implementing the C++03 behaviour, but this has crucially changed in C++11.
These are some relevant quotes from the C++03 and C++11 standards
In C++03:
To value-initialize an object of type T means:
— if T is a class type (clause 9) with a user-declared 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 non-union class type without a user-declared constructor, then every non-static data member and base-class component of T is value-initialized;
(emphasis mine)
In C++11:
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.
and
To zero-initialize an object or reference of type T means:
— if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;
- 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;
Note: The following only applies to C++03:
Either remove A
's user-provided constructor, or change it to
A() : i() {}
When you value-initialize a B
here,
B* p = new B {};
it value-initializes its data members. Since A
has a default constructor, the value-initialization results in a call to that. But that constructor does not explicitly initialize A::i
, so it gets default-initialized, which for an int
means no initialization is performed.
If you had not provided a default constructor for A
, then the data member would get zero-initialized when an A
is value-initialized.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With