Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a user-provided default constructor lead to an uninitialized member?

Tags:

c++

Please consider the following code:

#include <iostream>

struct A{ // with implicit default constructor
       int number;
};

struct B{
       int number;
       B(){}; // user-provided default constructor
};

int main()
{
    A aa = {};
    B bb = {};

    std::cout << "aa.number: " << aa.number << std::endl;
    std::cout << "bb.number: " << bb.number << std::endl;    
}

Running the code online results in the following output:

aa.number: 0
bb.number: 19715

Why is bb.number uninitialized? I thought that zero initialisation is guaranteed by using ={} ?

like image 916
BlueTune Avatar asked Nov 30 '22 21:11

BlueTune


2 Answers

I thought that zero initialisation is guaranteed by using ={} ?

That is only true if the type is "correct", which B is not. B bb = {}; will default construct a B and your default constructor, B(){};, doesn't initialize number so no matter what, number will never be initialized because that is how your default constructor works. If you had a "correct" constructor like

B() : number(0) {};
// or use
int number = 0;
B(){};

Then you'd get zero initialization of number when it is default constructed.

This isn't the case with A because A is an aggregate and those come with certain guarantees like zero initialization if an empty braced-init-list, technical name for the {}, is used.

like image 60
NathanOliver Avatar answered Dec 15 '22 04:12

NathanOliver


A is an aggregate type, because it doesn't have any user-provided constructors (and fulfills a few other requirements).

Therefore A aa = {}; does not call the implicitly generated default constructor. Instead initialization with brace-enclosed initializer lists performs aggregate initialization, which for an empty list means that the members are initialized as if by a {} initializer, which in turn means for a member of scalar type such as int that it will be initialized to zero.

B is not an aggregate type, because it does have a user-provided constructor.

Therefore B bb = {}; cannot do aggregate initialization and will call the default constructor instead. The default constructor (in either A or B) does not specify an initializer for the members and so the member is default-initialized, which for a fundamental type, such as int, means that it will not be set to any value. Its value will remain indeterminate.

Accessing the indeterminate value, which your program does, causes undefined behavior.


If you declare a constructor yourself, then it becomes that constructor's responsibility to initialize all the members appropriately. The rule that = {} or {} always initializes only holds under the assumption that a user-provided default constructor, if it exists, does the right thing in the sense that it provides sensible initializers to all its members and it doesn't have to mean zero-initialization necessarily.

like image 20
walnut Avatar answered Dec 15 '22 02:12

walnut