Details on the example uniform initialization syntax for C++11

  • 2020-05-30 20:54:22
  • OfStack

preface

This paper mainly introduces the relevant content of the initialization grammar of C++11. As for the grammar of the current new standard C++11, the variable legal initializer has the following form:


X a1 {v};
X a2 = {v};
X a3 = v;
X a4(v);

In fact, there is no essential difference between the first and second initializations above, and adding = is a customary behavior. The list initialization syntax using curly braces dates back to the days of C++98, but historically they were only used to initialize array elements and to initialize data of the custom POD type. Such as:


int v1[] = {1, 2, 3, 4};
int v2[5] = {1,2,3};
char msg = "hello, world!";

When initializing an array with a list, if the array size is not specified when the array is declared, the compiler automatically calculates the size of the array using the number of elements in its list. If an array size is provided, but the number of elements in the list is less than the array size, the system assigns all remaining elements to 0. If it is a character array, C++ also supports initialization using string brightness.

1. Unified 1 initializer for C++11

In the new standard C++11, the scope and features of this thing have been greatly expanded, and it has become a basic and important tool that can perform almost any initialization operation, so it is also known as "Uniform initialization", although it is still commonly called list initialization in China. Because he can avoid many problems and defects of traditional initialization, so from Bjarne Stroustrup grandpa's "C + + programming language to describe tone, it seems, are strongly recommended to use list is initialized, even used old initialization C + + programmer at first glance will be very not accustomed to, but C + + strongly recommended ways for series 1 above 1 initialization.

C++11 also introduces the atomic atomic type, a type of variable (e.g std::atomic ) can not use the traditional = method for initialization, can only use {} or () method for initialization; For a custom class, if its non-static member variable has a default value, the default value can only be initialized with {} or =. In short, only {} can be used in any location compared to other types, so it's no surprise that it's called a system 1 initializer.

Prevent type initialization narrowed the list of a very important feature, because there are many implicit C + + conversion operations, such as: implicit floating-point types into plastic, short long integer is converted to the integer result in data loss, high precision of the low precision of the data, but all the data transformation to convert back again after 1 time and can't get the original says, can be called type narrowing. Type narrowing often leads to lost data accuracy, and even potential intentionally or unintentionally errors, especially those who don't like to see compiler warnings programmers often overlooked these tips, and through the list of initialization of syntax, the compiler the mandatory inspections at compile time, in the event of type narrowing the mandatory compilation fails, able to put an end to the related problems.

In addition to the above advantages, list initialization syntax eliminates the dark side of C++ restructuring syntax. One of the beliefs of C++ is that any statement that can be interpreted as a declarative syntax will be interpreted as a declarative statement, leading to the incorrect use of the call to the default constructor to create the object.


Widget w(); //  Is interpreted as a function declaration 
Widget w{}; // OK

The other is that it is easy to confuse the semantics when the container is in use, when the list initialization syntax is used to indicate that the list we provide is the actual element. Because the constructor of the container class has the use std::initializer_list As the overloaded version, if you want to explicitly call one of its versions of the constructor, you need to use () to get around it std::initializer_list Is called ctor-resort.


vector<int> v1{99};   // 1 And the value is zero 99
vector<int> v2(99);   //  It's actually calling the constructor 99 The default value is zero 0
vector<string> v2("hello");  // Error , no matching constructor 

2. The dark side of the unified 1 initializer

Using the list initialization syntax is fine in most cases and works well, but 1 is the same std::initializer_list Combined, its use can feel confusing. When auto does automatic type derivation, {} is inferred as by default std::initializer_list If the result is not what you want, you need to circumvent it and initialize it in some other way.


auto z1 {99}; // initializer_list<int>
auto z2 = 99; // int

If you think avoiding the top hole is the end of the story, hehe... The biggest problem with the unified 1 initializer is its combination with the constructor. If the constructor of a class, it provides a receive std::initializer_list As overloaded versions of parameter types, the compiler will strongly prefer to use overloaded versions with initialization lists when constructing objects using the standard 1 initialization syntax.

We know that std::initializer_list As a parameter, in fact, the sum of the elements in the parameter list does not match T exactly, but only needs to be converted to T. In this case, as long as the conversion meets the requirements, the compiler will prefer to use it std::initializer_list An overloaded version of a parameter, even if the other overloaded constructors have a better match. During the conversion process, if the type promotion meets the requirements, it will be called normally. If a narrowing transformation occurs, the call fails and an error is reported. The overloading mechanism works only if untranslatable types such as strings and Numbers overload each other.


struct Widget {
 Widget(int i, bool b) { cout << "1" << endl; }
 Widget(int i, double d) { cout << "2" << endl; }
 Widget(std::initializer_list<bool> il) { cout << "3" << endl; }
};
Widget w1{1, true}; // 3
Widget w2{9, true}; // Error

There is also an extreme case where a custom class has both a default constructor and a custom class std::initializer_list The C++ standard explicitly calls its default constructor, and if you want to call version 2 in the semantics of an empty list, you can initialize it using ({}).

3. Default initialization behavior of C++ object

List initializers also allow the use of empty list {} as initializers, where elements are initialized with default values or the default constructor of a custom type is called, so the default behavior of variables initialized with a list is good.

For our custom data types, if necessary, we don't need a specific element type of T for a specific call, just convert it to T and use an iterator in the constructor to access each element in the list.

C++ states that if the defined variable does not specify an initializer, the global variable, namespace variable, local static variable, and static member will perform an empty list {} initialization of the corresponding data type. For local variables, variables on the free storage area (heap objects) will not be initialized by default unless they are defined in the default constructor of a user-defined type, which is a particular concern. Operating on uninitialized variables may cause uncertain behavior.


int* p{ new int{} };
char* q{ new char[2014]{} }

Oh, if you suddenly see 1 big tuo C++ code using {} initialization, it may be 1 time to feel strange, but get used to it!

conclusion

reference

Effective Modern C++ In-depth application of C++11: code optimization and engineering level applications C++ programming language (version 4 of the original book) GotW #1 Solution: Variable Initialization, or Is It?

Related articles: