Paragraph 8.5p7 of the C++11 Standard states:
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.
if T is an array type,
then each element is value-initialized;
otherwise, the object is
zero-initialized.
I have a problem understanding the characters in bold above. How the additional calling of T's implicit default constructor could alter the zero-initialization, that has just occurred in this case?
Here's a concrete example:
class A {
int a;
public:
A() : a(1) {}
};
class B {
int b;
A c;
};
B falls in this category - it's a non-union class type without a user-provided constructor. So if a B is value-initialized, it will first be zero-initialized (so both b and c.a will be set to 0), and then the default constructor will be called (which will call A's constructor and set c.a to 1).
By the as-if rule, these might be combined into a single step by the optimizer (which will set b to 0 and c.a to 1), as no-one can ever see the object between the zero initialization and the default constructor.
T may not have its own explicit default constructor, but it may derive from U that does, and/or have a member of class type V that does.
struct S {
int a, b;
S() : b(10) {}
};
struct T {
S s;
};
int main() {
S s{};
T t{};
}
t is value initialized and T does not have a user-provided constructor. However T's implicitly declared default constructor is not trivial.
s is also value initialized but S has a user provided constructor.
s.a will have an indeterminate value. but t.s.a is zero due to the zero initialization that precedes the call of the default constructor. Both s.b and t.s.b are set to the value 10.
Related
Per cppreference, the syntax for value initialization is:
[..]
T object {}; (since C++11)
[..]
It's already known that value-initialization is performed when an object is constructed with an empty initializer.
Per, [dcl.init]/8 (emphasis mine)
To value-initialize an object of type T means:
(8.1) if T is a (possibly cv-qualified) class type ([class]), then
(8.1.1) if T has either no default constructor ([class.default.ctor]) or a default constructor that is user-provided
or deleted, then the object is default-initialized;
(8.1.2) otherwise, the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T
has a non-trivial default constructor, the object is
default-initialized;
(8.2) [..]
(8.3) [..]
I interpret the term "no default constructor" as that there's no default constructor declared in the class. For example,
class S
{
long double d;
friend void f(const S&);
};
void f(const S& s) { std::cout << s.d; }
int main()
{
S s{ };
f(s); // 0
}
Since the class has "no default constructor", I'm expecting that the object s is default-initialized and the member s.d has indeterminate value. Why that's not the case?
I also have a confusion in understanding the point (8.1.1). How I can write this line of code T object {} without having a default constructor or with having a deleted default constructor? Notice the bold part, It's said that, "if T has either no default constructor or a default constructor that is deleted .."
Are there situations where objects of class types are value-initialized with deleted default constructor or without default constructor at all? Am I misreading 8.1.1?
Your class S does have a default constructor; it is just defined implicitly.
Per [class.default.ctor]/1:
A default constructor for a class X is a constructor of class X for which each parameter that is not a function parameter pack has a default argument (including the case of a constructor with no parameters). If there is no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted ([dcl.fct.def]). An implicitly-declared default constructor is an inline public member of its class.
Therefore S{} will zero-initialize the object per [dcl.init]/8.1.2.
When [dcl.init]/8.1.1 refers to classes with no default constructor it means classes where the implicit default constructor doesn't get generated because user-defined constructors exist. That is, (8.1.1) would apply to the following classes:
struct NoDefaultCtor
{
NoDefaultCtor(int) {}
};
struct UserProvidedDefaultCtor
{
UserProvidedDefaultCtor() {}
};
struct DeletedDefaultCtor
{
DeletedDefaultCtor() = delete;
};
For all three of those classes, value-initialization will perform default-initialization. In the case of NoDefaultCtor and DeletedDefaultCtor that default-initialization will fail, of course, but it's important that (8.1.1) catches those types so they don't fall through and get zero-initialized by (8.1.2).
I found many articles explaining the difference between "default-initialization and value-initialization" but in fact I didn't understand clearly.
Here's an example:
class A{
public:
int x;
};
int main(){
A a;// default initialization so x has undefined value.
A b = A(); // value initialization so x is a scalar thus it is value initialized to 0
}
Above it is OK as I guess but here:
int value = 4; // is this considered a value-initialization?
Please help me understand the major differences between the two forms of initializations.
A a; is default initialization, as the effect the default constructor of A is used for initialization. Since the implicitly-generated default constructor of A does nothing, a.x has indeterminate value.
A() is value initialization,
if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor;
Note the difference with default initialization, A has an implicitly-defined default constructor, and the object is zero-initialized; so the data memeber x of the temporary object A() will be initialized to 0.
A b = A(); is copy initialization, in concept b is initialized from the temporary object A(), so b.x will be initialized to 0 too. Note that because of copy elision, since C++17 b is guaranteed to be value-initialized directly; the copy/move construction is omitted.
int value = 4; is copy initialization too. value will be initialized to 4.
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 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.
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 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;
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.
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.
Integral types are not required to be initialized to a value like that in a non-default constructor (since you have provided a constructor)
Change your constructor to A() : i(0) {}.
In the following example:
#include <iostream>
struct A {
int z;
A(std::string) {}
A() = default;
};
int main() {
char buf[1000];
std::fill(buf, buf + 1000, 'x');
auto a = new(buf) A{};
std::cerr << "A.z: " << a->z << std::endl;
}
Compiled with GCC 4.8 outputs zero (same behavior with Clang 3.4). This seems to indicate that a is being zero-initialized before the default constructor is called.
But according to value-initialization rules on cppreference.com, the object should not be initialized before the default constructor invocation. Class A qualifies for bullet point #1 under C++11:
1) If T is a class type with at least one user-provided constructor of any kind, the default constructor is called.
Another perhaps useful data point is that if we replace A() = default with A() {} in the above example, no zero-initialization happens as expected. This seems to indicate that bullet point #2 of value initialization is being followed in the initial example which would be wrong:
2) If T is an non-union class type without any user-provided constructors, then the object is zero-initialized and then the implicitly-declared default constructor is called (unless it's trivial)
The wording that you cite for value initialization in C++11 was considered defective, see Core Working Group DR 1368 and the resolution in Core Working Group DR 1301 that changed the wording (showing additions with bold and deletions with strike through):
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 9 [class]) with either no default constructor (12.1 [class.ctor]) or a default constructor that is user-provided or deleted constructor (12.1 [class.ctor]), then the object is default-initialized 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 or deleted default constructor, then the object is zero-initialized and, if T's implicitly-declared default constructor is T has a non-trivial default constructor, that constructor is called. default-initialized;
Some compilers - I believe GCC 4.8 and clang since 3.3ish - have already implemented the resolution of DR 1301.
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 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.
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 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;
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.
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.
Integral types are not required to be initialized to a value like that in a non-default constructor (since you have provided a constructor)
Change your constructor to A() : i(0) {}.