Value initialization of nested classes - c++

By the rules of value initialization. Value initialization occurs:
1,5) when a nameless temporary object is created with the initializer
consisting of an empty pair of parentheses or braces (since C++11);
2,6) when an object with dynamic storage duration is created by a
new-expression with the initializer consisting of an empty pair of
parentheses or braces (since C++11);
3,7) when a non-static data
member or a base class is initialized using a member initializer with
an empty pair of parentheses or braces (since C++11);
4) when a named
variable (automatic, static, or thread-local) is declared with the
initializer consisting of a pair of braces.
Trivial example
struct A{
int i;
string s;
A(){};
};
A a{}
cout << a.i << endl // default initialized value
without explicitly declared constructor and left with defaulted default ctor // compiler generated one we get.
struct A{
int i;
string s;
};
A a{};
cout << a.i << endl // zero-initialized value
However using antoher struct.
struct A{
int i;
string s;
};
struct B{
A a;
int c;
};
B a{};
cout << a.a.i << endl // default initialized , even tho we did not , int struct B , declared A a{}.
The value of a.i is zero-initialized, even without using {} / () construct, which goes against rules ( if i am not mistaken ).
Using same logic on struct B:
struct A{
int i;
string s;
};
struct B{
A a;
int c;
};
B b;
cout << b.c << endl; // default inicialized
We get behavior according to rules.
The last example:
struct A
{
int i;
A() { }
};
struct B { A a; };
std::cout << B().a.i << endl;
B().a.i is also zero-initialized while we explicitly declared constructor and its not deleted.
Why are the these values getting zero-initialized? By rules stated here they should be default-initialized not zero-initialized.
Thanks for answers.

It's the difference between A being an aggregate or not.
[dcl.init.aggr] (Emphasis mine)
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),
So when A has no declared constructors, saying A a{} has the effect of aggregate initialization
which will construct each member with an empty initialization list:
If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member
not explicitly initialized shall be initialized from an empty initializer list
So you get int{} and std::string{} which will value-initialize the integer member to zero.
When you do provide a default constructor, the aggregate property is lost and the int member remains uninitialized, so accessing it is considered undefined behavior.
To be specific:
This code is undefined behavior when accessing a.i because you provided a user-defined constructor, so the int i field remains uninitialized after construction:
struct A{
int i;
string s;
A(){};
};
A a{} ;
cout << a.i << endl;
And this code exhibits undefined behavior when accessing b.c because you did not perform list initialization on B b:
struct B{
A a;
int c;
};
B b;
cout << b.c << endl;
All the other code is okay, and will zero-initialize the integer fields. In the scenarios where you are using braces {}, you are performing aggregate-initialization.
The last example is a little tricky because you're performing value-initialization. Since B is an aggregate, it gets zero-initialized ([dcl.init]), in which:
each base-class
subobject is zero-initialized
So again you're okay accessing the integer member of the A subobject.

According to the rules of aggregate initialization, the members are indeed value initialized.
Value initialization:
In all cases, if the empty pair of braces {} is used and T is an aggregate type, aggregate-initialization is performed instead of value-initialization.
The effects of aggregate initialization are:
If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.

Related

Is a float member guaranteed to be zero initialized with {} syntax?

In C++17, consider a case where S is a struct with a deleted default constructor and a float member, when S is initialized with empty braces, is the float member guaranteed by the standard to be zero-initialized?
struct A {
int x{};
};
struct S
{
S() = delete;
A a;
float b;
};
int main()
{
auto s = S{}; // Is s.b guaranteed to be zero?
}
In my opinion, cppreference.com is not clear, saying both that:
If the number of initializer clauses is less than the number of members and basesor initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default member initializers, if provided in the class definition, and otherwise (since C++14) copy-initialized from empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.
(from here), which implies that b is guaranteed to be zero
In all cases, if the empty pair of braces {} is used and T is an aggregate type, aggregate-initialization is performed instead of value-initialization.
(from here)
which implies that b is not guaranteed to be zero.
There is also a discussion that seems to imply that while not guaranteed, all known compiler zero-initialize anyway:
The standard specifies that zero-initialization is not performed when the class has a user-provided or deleted default constructor, even if that default constructor is not selected by overload resolution. All known compilers performs additional zero-initialization if a non-deleted defaulted default constructor is selected.
Related to Why does aggregate initialization not work anymore since C++20 if a constructor is explicitly defaulted or deleted?
This is a quirk of C++ that is fixed in C++20. In the meantime you can add explicit to the deleted default constructor to force the struct to become non-aggregate, and make your code a guaranteed compile error:
struct A {
int x{};
};
struct S
{
explicit S() = delete;
const A a;
const float b;
};
int main()
{
auto s = S{}; // error: call to deleted constructor of 'S'
}
Because S is an aggregate, S{} will perform aggregate initialization. The rule in the standard about how members are initialized when there are no initializers in the list is basically what you cited:
If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.
Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
So for b, that's the equivalent of float b = {};. Per the rules of list initialization, we have to get all the way down to 3.10:
Otherwise, if the initializer list has no elements, the object is value-initialized.
And value initialization will initialize a float to 0.

C++ zero initialization without constructor

I don't understand what happens regarding the zero initialization of structs that has default values for its members.
If I have these structs:
struct A {
int *a;
int b;
};
struct B {
int *a;
int b;
B() : b(3) {}
};
struct C {
int *a;
int b = 3;
};
What we can say without a doubt is:
A a; leaves all fields uninitialized
A a{}; is {nullptr, 0}
B b; and B b{}; both are {garbage, 3} (the constructor is called)
Now it's unclear what happens when I do the following, here are the results using gcc:
C c; // {garbage, 3}
C c{}; // {nullptr, 3}
The question is: does C c{}; guarantees that C::a is initialized to nullptr, in other words, does having default members like in C still zero initialize the other members if I explicitly construct the object like C c{};?
Because it's not what happens if I have a constructor that does the same thing than C (like in B), the other members are not zero initialized, but why? What is the difference between B and C?
As of C++14, C is an aggregate (like A), and C c{} syntax performs aggregate initialization. This includes, in part:
[dcl.init.aggr]/8 If there are fewer initializer-clauses in the list than there are elements in a non-union aggregate, then each element not explicitly initialized is initialized as follows:
(8.1) — If the element has a default member initializer (12.2), the element is initialized from that initializer.
(8.2) — Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list (11.6.4).
(8.3) — Otherwise, the program is ill-formed.
So C c{}; is equivalent to C c{{}, 3};. Initializing an int* member with empty list causes it to be zero-initialized.
In C++11, C is not an aggregate (having a default member initializer was disqualifying), and C c{}; calls an implicitly-defined constructor that leaves c.a member uninitialized.
In all versions of the standard, B is not an aggregate due to the user-defined constructor. B b{}; calls that constructor, which explicitly initializes b member and chooses to leave a uninitialized.
Aggregate initialization - cppreference.com
If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are value-initialized. If a member of a reference type is one of these remaining members, the program is ill-formed.
(until C++11)
If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default member initializers, if provided in the class definition, and otherwise (since C++14) copy-initialized from empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.
(since C++11)
So A a{}; all members are default initialized

initializing struct with {0}

I'm debugging some code that essentially is identical to this:
struct Foo { int a; int b; };
struct Bar { Bar() {} Foo foo{0}; };
When I make an instance of Bar, it seems like both a and b are initialized to zero. Is this guaranteed? Where can I find that in the spec?
According to cppreference.com
If the number of initializer clauses is less than the number of members [and bases (since C++17)] or initializer list is completely empty, the remaining members [and bases (since C++17)] are initialized [by their default member initializers, if provided in the class definition, and otherwise (since C++14)] by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.
Foo has no default member initializers (int b{0};), so b will be initialized by list-initialization with an empty list, which means value-initialization for non-class types: b = int() // = 0.

Initializing member variables of a struct in c++

I have a struct with a few double values:
struct A {
double a;
double b;
}
if I create a new struct, e.g. A a, are all the members (e.g. a.a) initialized to zeroes automatically in C++?
Not by default (unless it's a variable of static storage - that is, a static or global variable).
There are a few ways to initialize a struct of this kind to "zeros":
A a = { 0.0, 0.0 };
A a = { };
A a = A();
or if you have a C++11 compatible compiler:
A a{0.0, 0.0};
A a{}
or add a constructor to the struct definition:
struct A {
double a;
double b;
A() : a(0.0), b(0.0) {}
};
8.5. Initializers [dcl.init] / 11.
If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an
object with automatic or dynamic storage duration has indeterminate value. [ Note: Objects with static or
thread storage duration are zero-initialized, see 3.6.2. — end note ]
and (ordering reversed for readability):
8.5. Initializers [dcl.init] / 6.
To default-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9), the default constructor for T is called (and the
initialization is ill-formed if T has no accessible default constructor);
— if T is an array type, each element is default-initialized;
— otherwise, no initialization is performed. [emphasis mine]
If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type
with a user-provided default constructor.
They are default initialized. For builtin types like int or double, their value depends on where the struct is declared (as a rule of thumb (but just as that): Assume they are always garbage unless initialized).
In global scope or/and with static storage, they are all zeroes (incl. when the struct is a member of a struct which is at global scope).
At function-local scope, they are full of garbage.
Example:
#include <iostream>
struct Foo {
int x;
int y;
};
Foo foo;
int main () {
Foo bar;
std::cout << foo.x << ":" << foo.y << '\n';
std::cout << bar.x << ":" << bar.y << '\n';
}
This on the first run gives me
0:0
-1077978680:12574708
On the second run, without recompilation, this gives me:
0:0
-1075556168:12574708
A POD-struct can be initialized all zeroes using e.g. memset or just ...
Foo foo = {0}; // C and C++03
Foo foo{0}; // C++11
No. In the general case, they have unspecified values.
If you don't like this behaviour, you can provide a constructor:
struct A {
double a;
double b;
A(): a(0.0), b(0.0) {}
}

Value initialization for non aggregate class with user defined constructor

I am going through this question:Aggregates and pods
When an object of class in C++ with user defined default constructor, has only some of it's data members initialized,will the rest of data members be value initialized?
Following is the program I tried resulting in compilation error:
#include <iostream>
using namespace std;
class A {
public:
A() {
i=10;
f = 10.0f;
c = 45;
d = 10.0;
}
void show() {
cout << i << "\t" << f << "\t" << c << "\t" << d<<"\n";
}
private:
int i;
float f;
char c;
double d;
};
int main() {
A a={20,20.0f};
a.show();
}
Your class does not qualify as an Aggregate because it has private non-static data members.
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).
EDIT:
For objects of non aggregate classes if only some of the data members are initialized, will the rest be value initialized(assigned with 0)?
The rules are specified in:
C++11 8.5.4 List-initialization [dcl.init.list] Para 3:
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).
— Otherwise, if T is a specialization of std::initializer_list, an initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (8.5).
— Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.
— Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary. [ Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type. —end note ]
— Otherwise, if the initializer list has a single element, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.
— Otherwise, if the initializer list has no elements, the object is value-initialized.
— Otherwise, the program is ill-formed.
Your program falls in none of the scenarios mentioned and hence falls under the the ill formed case.
You have to declare constructor with 4 arguments like
A(int i = 10, float f = 10.0f, int c = 45, float d = 10.0f):
i(i), f(f), c(c), d(d)
{
}
Now you can initialize your a variable with braces