5
class A { public: int x[100]; };

Declaring A a will not initialize the object (to be seen by garbage values in the field x). The following will trigger initialization: A a{} or auto a = A() or auto a = A{}.

Should any particular one of the three be preferred?

Next, let us make it a member of another class:

class B { public: A a; };

The default constructor of B appears to take care of initialization of a. However, if using a custom constructor, I have to take care of it. The following two options work:

class B { public: A a;  B() : a() { } };

or:

class B { public: A a{};  B() { } };

Should any particular one of the two be preferred?

Lasse Kliemann
  • 279
  • 2
  • 8
  • Please clarify your question regarding the context in which you would like to use the initializers. Are you only interested in empty initializers? Are you looking to determine a more general approach for complex objects? – wally Mar 29 '16 at 15:35

1 Answers1

3

Initialization

class A { public: int x[100]; };

Declaring A a will not initialize the object (to be seen by garbage values in the field x).

Correct A a is defined without an initializer and does not fulfill any of the requirements for default initialization.


1) The following will trigger initialization:

A a{};

Yes;


2) The following will trigger initialization:

auto a = A();

Yes;

  • This is copy initialization where a prvalue temporary is constructed with direct initialization () which
  • The prvalue temporary is then used to direct-initialize the object.
  • Copy elision may be, and normally is employed, to optimize out the copy and construct A in place.
    • Side effects of skipping copy/move constructors are allowed.
  • Move constructor may not be deleted. e.g A(A&&) = delete;
  • If copy constructor is deleted then move constructor must be present. e.g. A(const A&) = delete; A(A&&) = default;
  • Will not warn of narrowing conversion.

3) The following will trigger initialization:

auto a = A{}

Yes;

  • This is copy initialization where a prvalue temporary is constructed with list initialization {} which
  • Copy elision may be, and normally is employed, to optimize out the copy and construct A in place.
    • Side effects of skipping copy/move constructors are allowed.
  • Move constructor may not be deleted. e.g A(A&&) = delete;
  • If copy constructor is deleted then move constructor must be present. e.g. A(const A&) = delete; A(A&&) = default;
  • Will warn of narrowing conversion.
  • Works even if the default constructor is deleted. e.g. A() = delete; (If 'A' is still considered an aggregate)

Should any particular one of the three be preferred?

Clearly you should prefer A a{}.


Member Initialization

Next, let us make it a member of another class:

class B { public: A a; };

The default constructor of B appears to take care of initialization of a.

No this is not correct.

  • the implicitly-defined default constructor of 'B' will call the default constructor of A, but will not initialize the members. No direct or list initialization will be triggered. Statement B b; for this example will call the default constructor, but leaves indeterminate values of A's array.

1) However, if using a custom constructor, I have to take care of it. The following two options work:

class B { public: A a;  B() : a() { } };

This will work;

2) or:

class B { public: A a{};  B() { } };

This will work;

Clearly you should prefer the second option.


Personally, I prefer using braces everywhere, with some exceptions for auto and cases where a constructor could mistake it for std::initializer_list:

class B { public: A a{}; };

A std::vector constructor will behave differently for std::vector<int> v1(5,10) and std::vector<int> v1{5,10}. with (5,10) you get 5 elements with the value 10 in each one, but with {5,10} you get two elements containing 5 and 10 respectively because std::initializer_list is strongly preferred if you use braces. This is explained very nicely in item 7 of Effective Modern C++ by Scott Meyers.

Specifically for member initializer lists, two formats may be considered:

In member initializer lists, fortunately, there is no risk of the most vexing parse. Outside of the initializer list, as a statement on its own, A a() would have declared a function vs. A a{} which would have been clear. Also, list initialization has the benefit of preventing narrowing conversions.

So, in summary the answer to this question is that it depends on what you want to be sure of and that will determine the form you select. For empty initializers the rules are more forgiving.

Community
  • 1
  • 1
wally
  • 10,717
  • 5
  • 39
  • 72
  • Could you please elaborate on what could be the implications of the constructor mistaking the `{}` in `a{}` for `std::initializer_list`? – Lasse Kliemann Mar 29 '16 at 08:04
  • My question was specifically for empty braces. I found the answer in the book that you pointed out: empty braces will call the default ctor, and not the initializer-list ctor with an empty list. Hence the empty `()` and `{}` should behave exactly the same. – Lasse Kliemann Mar 29 '16 at 14:11
  • Interesting point about the empty braces. I've updated the answer to include some additional points. – wally Mar 29 '16 at 15:31
  • Speaking of most vexing parse, one observation relevant outside of init lists: `{}` instead of `()` can avoid it, but this is only necessary in certain situations. The problem seems to be that it is allowed to write the argument list to the ctor (even if empty) next to the object that is being defined, that is, `A a()`. To correct this, use `A a{}`. However, the following is ok as well: `A a = A()` or `auto a = A()`. Now more and more people seem to write `auto a = A{}` and say that "this" prevents most vexing parse. This claim can be confusing since the `{}` do not make the difference here. – Lasse Kliemann Mar 30 '16 at 07:44
  • @LasseKliemann Sometimes the form makes a difference. I've made some updates. – wally Mar 30 '16 at 20:51