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.