std::atomic ai1 {0 }; // fine
std::atomic ai2 (0 ); // fine
std::atomic ai3 =0; // error!
It's thus easy to understand why braced initialization is called “uniform.” Of C++'s three ways to designate an initializing expression, only braces can be used everywhere.
A novel feature of braced initialization is that it prohibits implicit narrowing conversions among built-in types. If the value of an expression in a braced initializer isn't guaranteed to be expressible by the type of the object being initialized, the code won't compile:
double x, y, z;
…
int sum1 {x + y + z }; // error! sum of doubles may
// not be expressible as int
Initialization using parentheses and “ =” doesn't check for narrowing conversions, because that could break too much legacy code:
int sum2 (x + y + z ); // okay (value of expression
// truncated to an int)
int sum3 =x + y + z; // ditto
Another noteworthy characteristic of braced initialization is its immunity to C++'s most vexing parse . A side effect of C++'s rule that anything that can be parsed as a declaration must be interpreted as one, the most vexing parse most frequently afflicts developers when they want to default-construct an object, but inadvertently end up declaring a function instead. The root of the problem is that if you want to call a constructor with an argument, you can do it like this,
Widget w1 (10); // call Widget ctor with argument 10
but if you try to call a Widget constructor with zero arguments using the analogous syntax, you declare a function instead of an object:
Widget w2 (); // most vexing parse! declares a function
// named w2 that returns a Widget!
Functions can't be declared using braces for the parameter list, so default-constructing an object using braces doesn't have this problem:
Widget w3 {}; // calls Widget ctor with no args
There's thus a lot to be said for braced initialization. It's the syntax that can be used in the widest variety of contexts, it prevents implicit narrowing conversions, and it's immune to C++'s most vexing parse. A trifecta of goodness! So why isn't this Item entitled something like “Prefer braced initialization syntax”?
The drawback to braced initialization is the sometimes-surprising behavior that accompanies it. Such behavior grows out of the unusually tangled relationship among braced initializers, std::initializer_lists, and constructor overload resolution. Their interactions can lead to code that seems like it should do one thing, but actually does another. For example, Item 2explains that when an auto-declared variable has a braced initializer, the type deduced is std::initializer_list, even though other ways of declaring a variable with the same initializer would yield a more intuitive type. As a result, the more you like auto, the less enthusiastic you're likely to be about braced initialization.
In constructor calls, parentheses and braces have the same meaning as long as std::initializer_listparameters are not involved:
class Widget {
public:
Widget(int i, bool b); // ctors not declaring
Widget(int i, double d); // std::initializer_list params
…
};
Widget w1 (10, true ); // calls first ctor
Widget w2 {10, true }; // also calls first ctor
Widget w3 (10, 5.0 ); // calls second ctor
Widget w4 {10, 5.0 }; // also calls second ctor
If, however, one or more constructors declare a parameter of type std::initializer_list, calls using the braced initialization syntax strongly prefer the overloads taking std::initializer_lists. Strongly . If there's any way for compilers to construe a call using a braced initializer to be to a constructor taking a std::initializer_list, compilers will employ that interpretation. If the Widget class above is augmented with a constructor taking a std::initializer_list, for example,
class Widget {
public:
Widget(int i, bool b); // as before
Widget(int i, double d); // as before
Widget(std::initializer_list il);// added
…
};
Widgets w2and w4will be constructed using the new constructor, even though the type of the std::initializer_listelements ( long double) is, compared to the non- std::initializer_listconstructors, a worse match for both arguments! Look:
Widget w1 (10, true ); // uses parens and, as before,
// calls first ctor
Widget w2 {10, true }; // uses braces, but now calls
// std::initializer_list ctor
// (10 and true convert to long double)
Widget w3 (10, 5.0 ); // uses parens and, as before,
// calls second ctor
Widget w4 {10, 5.0 }; // uses braces, but now calls
// std::initializer_list ctor
// (10 and 5.0 convert to long double)
Even what would normally be copy and move construction can be hijacked by std::initializer_listconstructors:
class Widget {
public:
Widget(int i, bool b); // as before
Widget(int i, double d); // as before
Widget(std::initializer_list il); // as before
operator float() const; // convert
… // to float
};
Widget w5 (w4 ); // uses parens, calls copy ctor
Widget w6 {w4 }; // uses braces, calls
// std::initializer_list ctor
// (w4 converts to float, and float
// converts to long double)
Widget w7 (std::move(w4) ); // uses parens, calls move ctor
Widget w8 {std::move(w4) }; // uses braces, calls
// std::initializer_list ctor
// (for same reason as w6)
Compilers' determination to match braced initializers with constructors taking std::initializer_lists is so strong, it prevails even if the best-match std::initializer_listconstructor can't be called. For example:
class Widget {
public:
Widget(int i, bool b); // as before
Widget(int i, double d); // as before
Widget(std::initializer_list< bool> il); // element type is
// now bool
… // no implicit
}; // conversion funcs
Читать дальше