At the risk of being a bit tangential, let me post a modified version of the problem, along with a discussion. Let's consider these classes:
struct Agatha
{
int x;
};
struct Claire
{
Claire() { }
int x;
};
To simplify the initialization syntax and make the example analyzable with Valgrind, let's use dynamic objects:
#include <memory>
#include <iostream>
int main()
{
std::unique_ptr<Agatha> p1(new Agatha); // #1d
std::unique_ptr<Agatha> p2(new Agatha()); // #1v
std::unique_ptr<Claire> q1(new Claire); // #2d
std::unique_ptr<Claire> q2(new Claire()); // #2v
std::cout << p1->x
<< p2->x
<< q1->x
<< q2->x
<< std::endl;
}
Only one of those printing lines is correct! Can you figure out which one?
The answer is #1v. Let's discuss:
Case #1d default-initializes an aggregate, which default-initializes each member, which default-initializes Agatha::x
, which leaves it uninitialized.
Case #1v value-initializes a aggregate, which value-initializes all members, and thus value-initializes Agatha::x
, and thus zero-initializes it. (This is the good case.)
Case #2d default-initializes a non-aggregate, which calls the default constructor. The default constructor of Claire
does not initialize Claire::x
, so it is left uninitialized.
Case #2d value-initialization of a non-aggregate also calls the default constructor, and the situation is identical to #2v.
Notice that the whole discussion has nothing to do with PODness, but merely with whether the class is an aggregate, like Agatha
, or a non-trivial class type with like Claire
. You could add a member of type std::string
to Agatha
without affecting this example.