问题
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?
回答1:
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;
a{}
performs list initialization which- becomes value initialization if
{}
is empty, or could be aggregate initialization ifA
is an aggregate. - Works even if the default constructor is deleted. e.g.
A() = delete;
(If 'A' is still considered an aggregate) - Will warn of narrowing conversion.
2) The following will trigger initialization:
auto a = A();
Yes;
- This is copy initialization where a prvalue temporary is constructed with direct initialization
()
which- uses value initialization if the
()
is empty. - No hope of aggregate initialization.
- uses value initialization if the
- 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- uses value initialization if
{}
is empty, or could be aggregate initialization ifA
is an aggregate. - The prvalue temporary is then used to direct-initialize the object.
- uses value initialization if
- 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 ofa
.
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. StatementB b;
for this example will call the default constructor, but leaves indeterminate values ofA
'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;
: a()
is a constructor initializer anda()
is a member initializer as part of the member initializer list.- Uses direct initialization
()
or, if()
is empty, value initialization. - No hope of using aggregate initialization.
- Will not warn of narrowing conversion.
2) or:
class B { public: A a{}; B() { } };
This will work;
a
now has a non-static data member initializer, which may require a constructor to initialize it if you are using aggregate initialization and the compiler is not fully C++14 compliant.- The member initializer uses list initialization
{}
which - may become either value initialization if
{}
is empty or aggregate initialization ifA
is an aggregate. - If
a
is the only member then the default constructor does not have to be defined and the default constructor will be implicitly defined.
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:
- Direct initialization
a()
which becomes value initialization if the()
is empty. - List initialization
a{}
which also becomes value initialization if{}
is empty.
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.
来源:https://stackoverflow.com/questions/36270612/preferred-way-of-class-member-initialization