0

So let's say I'm working with this toy example:

struct Foo {
    int member;
};

I know that the default constructor won't default initialize member. So if I do this, member remains uninitialized: const Foo implicit_construction. As an aside this seems to work fine: const Foo value_initialization = Foo() though my understanding is that this isn't actually using the default constructor.

If I change Foo like this:

struct Foo {
    Foo() = default;
    int member;
};

And I try to do const Foo defaulted_construction, unsurprisingly it behaves exactly as implicit_construction did, with member being uninitialized.

Finally, if I change Foo to this:

struct Foo {
    Foo(){};
    int member;
};

And I do: const Foo defined_construction member is zero-initialized. I'm just trying to make sense of what the implicitly defined constructor looks like. I would have though it would have been Foo(){}. Is this not the case? Is there some other black magic at work that makes my defined constructor behave differently than the defaulted one?


Edit:

Perhaps I'm being mislead here. defaulted_construction is definitely uninitialized.
While defined_construction is definitely initialized.

I took this to be standardized behavior, is that incorrect?

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • "*Finally, if I change Foo to this: `struct Foo { Foo(){}; int member; };` And I do: `const Foo defined_construction` `member` is zero-initialized*" - no, it is not. The value of `member` is still uninitialized and indeterminate. It just HAPPENS to be 0 in your test case, but that is still not a guarantee. If you want `member` to be initialized to 0, you have to do it yourself: `struct Foo { Foo() : member(0) {}; int member; };` or, in C++11 and later: `struct Foo { int member = 0; };` – Remy Lebeau May 30 '18 at 16:36
  • @RemyLebeau Ummm... shouldn't [this](https://ideone.com/KlEadm) give me an uninitialized warning then? [This](https://ideone.com/cLPvak) certainly does. – Jonathan Mee May 30 '18 at 17:08
  • 1
    No, the 1st case should not issue a warning, because an explicit user-defined constructor is used, and the compiler has no clue whether that constructor initializes `member` or not when called. That would require the compiler to perform a deep analysis of what the constructor does, instead of simply calling it. In the 2nd case, the compiler generates its own constructor, and so it knows that constructor does not initialize `member`, so it can issue a warning when that constructor is used in a context that an uninitialized `member` may cause issues. – Remy Lebeau May 30 '18 at 18:43
  • @RemyLebeau So I'm really working with uninitialized memory in `member` in *each* case. The only way to zero the value is to have a constructor that explicitly initializes `member` or without a constructor to do: `const Foo foo = Foo()` Is that correct? – Jonathan Mee May 30 '18 at 18:55
  • Yes, if you want `member` initialized to 0 correctly, you have to initialize it explicitly. – Remy Lebeau May 30 '18 at 18:57

1 Answers1

3

What you're experiencing is called default initialization and the rules for it are (emphasis mine):

  • if T is a non-POD (until C++11) class type, the constructors are considered and subjected to overload resolution against the empty argument list. The constructor selected (which is one of the default constructors) is called to provide the initial value for the new object;
  • if T is an array type, every element of the array is default-initialized;
  • otherwise, nothing is done: the objects with automatic storage duration (and their subobjects) are initialized to indeterminate values.

Edit, in response to OP's request below:

Note that declaring a constructor = default does not change the situation (again, emphasis mine):

Implicitly-defined default constructor

If the implicitly-declared default constructor is not defined as deleted, it is defined (that is, a function body is generated and compiled) by the compiler if odr-used, and it has exactly the same effect as a user-defined constructor with empty body and empty initializer list. That is, it calls the default constructors of the bases and of the non-static members of this class.

Since the default constructor has an empty initializer list, its members satisfy the conditions for default initialization:

Default initialization is performed in three situations:

...

3) when a base class or a non-static data member is not mentioned in a constructor initializer list and that constructor is called.

Also note that you have to be careful when experimentally confirming this, because it's entirely possible that the default-initialized value of an int may be zero. In particular, you mentioned that this:

struct Foo {
    Foo(){};
    int member;
} foo;

Results in value-initialization, but it does not; here, member is default-initialized.

Edit 2:

Note the following distinction:

struct Foo {
    int member;
};

Foo a; // is not value-initialized; value of `member` is undefined
Foo b = Foo(); // IS value-initialized; value of `member` is 0

This behavior can be understood by following the rules for value-initialization:

Value initialization is performed in these situations:

1,5) when a nameless temporary object is created with the initializer consisting of an empty pair of parentheses;

Form 1 (T();) is the form used on the right-hand side of the = above to initialize b.

The effects of value initialization are:

1) if T is a class type with no default constructor or with a user-provided or deleted default constructor, the object is default-initialized;

2) if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor;

3) if T is an array type, each element of the array is value-initialized;

4) otherwise, the object is zero-initialized.

Finally though, note that in our earlier example:

struct Foo {
    Foo(){}; // this satisfies condition (1) above
    int member;
};

Foo f = Foo();

Now, condition (1) applies, and our (empty) user-declared constructor is called instead. Since this constructor does not initialize member, member is default-initialized (and its initial value is thus undefined).

TypeIA
  • 16,916
  • 1
  • 38
  • 52
  • I'm sorry but I've moved the goalposts on you just a bit. I've added in links to gcc code behaving as I describe... which is inconsistent with what your quote says will happen. Can you have a look and let me know if I'm missing something? – Jonathan Mee May 30 '18 at 17:17
  • One final clarification and then I'll accept. [This answer](https://stackoverflow.com/a/2418195/2642059) discusses how `const Foo value_initialization = Foo()` zero-initializes primitives if the default constructor is the implicitly declared one. It's explaining that the `Foo()` here is in fact not using the constructor at all. Is that the case? – Jonathan Mee Jun 01 '18 at 12:38
  • Maybe "Edit 2" above will clarify further. – TypeIA Jun 01 '18 at 14:57
  • 2
    Thank you so much for your clarifications. This is really an excellent answer. – Jonathan Mee Jun 01 '18 at 15:07