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.