0

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.

Darlyn
  • 4,715
  • 12
  • 40
  • 90
  • The last example is from cppreference ( link ). And its stated its zero-initialized. – Darlyn Nov 03 '17 at 13:44
  • I spoke to soon in my original comment. In the very first example accessing `a.i` is UB. In the second it is aggregate initialization, so it's okay. The third is also aggregate initialization, so it's okay. accessing `b.c` in the fourth example is UB. In the fifth example, accessing `a.i` is okay because of the first rule your cited. – AndyG Nov 03 '17 at 14:08

2 Answers2

3

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.

AndyG
  • 39,700
  • 8
  • 109
  • 143
  • I'm pretty sure that it doesn't matter if `A` has a `std::string` as member or not. Either way, it is still an aggregate (no constructors, private/protected variables, no base classes, no virtual functions). I realize OP has edited their question :/ Well, just an FYI. – Rakete1111 Nov 03 '17 at 13:48
  • @Rakete1111: Thanks. I should've double checked on that. I thought that having a non-aggregate member disqualified it from being an aggregate itself. (This is only true for PODs) I updated the post to reflect that. – AndyG Nov 03 '17 at 14:00
0

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:

  1. 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.
Community
  • 1
  • 1
Felix Glas
  • 15,065
  • 7
  • 53
  • 82