Why is list initialization (using curly braces) better than the alternatives?

MyClass a1 {a};     // clearer and less error-prone than the other three
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);

Why?

I couldn't find an answer on SO, so let me answer my own question.


Basically copying and pasting from Bjarne Stroustrup's "The C++ Programming Language 4th Edition":

List initialization does not allow narrowing (§iso.8.5.4). That is:

  • An integer cannot be converted to another integer that cannot hold its value. For example, char to int is allowed, but not int to char.
  • A floating-point value cannot be converted to another floating-point type that cannot hold its value. For example, float to double is allowed, but not double to float.
  • A floating-point value cannot be converted to an integer type.
  • An integer value cannot be converted to a floating-point type.
  • Example:

    void fun(double val, int val2) {
    
        int x2 = val; // if val==7.9, x2 becomes 7 (bad)
    
        char c2 = val2; // if val2==1025, c2 becomes 1 (bad)
    
        int x3 {val}; // error: possible truncation (good)
    
        char c3 {val2}; // error: possible narrowing (good)
    
        char c4 {24}; // OK: 24 can be represented exactly as a char (good)
    
        char c5 {264}; // error (assuming 8-bit chars): 264 cannot be 
                       // represented as a char (good)
    
        int x4 {2.0}; // error: no double to int value conversion (good)
    
    }
    

    The only situation where = is preferred over {} is when using auto keyword to get the type determined by the initializer.

    Example:

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

    Conclusion

    Prefer {} initialization over alternatives unless you have a strong reason not to.


    There are MANY reasons to use brace initialization, but you should be aware that the initializer_list<> constructor is preferred to the other constructors , the exception being the default-constructor. This leads to problems with constructors and templates where the type T constructor can be either an initializer list or a plain old ctor.

    struct Foo {
        Foo() {}
    
        Foo(std::initializer_list<Foo>) {
            std::cout << "initializer list" << std::endl;
        }
    
        Foo(const Foo&) {
            std::cout << "copy ctor" << std::endl;
        }
    };
    
    int main() {
        Foo a;
        Foo b(a); // copy ctor
        Foo c{a}; // copy ctor (init. list element) + initializer list!!!
    }
    

    Assuming you don't encounter such classes there is little reason not to use the intializer list.


    There are already great answers about the advantages of using list initialization, however my personal rule of thumb is NOT to use curly braces whenever possible, but instead make it dependent on the conceptual meaning:

  • If the the object I'm creating conceptually holds the values I'm passing in the constructor (eg containers, POD structs, atomics, smart pointers etc.), then I'm using the braces.
  • If the constructor resembles a normal function call (it performs some more or less complex operations that are parameterized by the arguments) then I'm using the normal function call syntax.
  • For default initialization I always use curly braces.
    For one, that way I'm always sure that the object gets initialized irrespective of whether it eg is a "real" class with a default constructor that would get called anyway or a builtin / POD type. Second it is - in most cases - consistent with the first rule, as a default initialized object often represents an "empty" object.
  • In my experience, this ruleset can be applied much more consistently than using curly braces by default, but having to explicitly remember all the exceptions when they can't be used or have a different meaning than the "normal" function-call syntax with parenthesis (calls a different overload).

    It eg fits nicely with standard library-types like std::vector :

    vector<int> a{10,20};   //Curly braces -> fills the vector with the arguments
    
    vector<int> b(10,20);   //Parentesis -> uses arguments to parameterize some functionality,                          
    vector<int> c(it1,it2); //like filling the vector with 10 integers or copying a range.
    
    vector<int> d{};      //empty braces -> default constructs vector, which is equivalent
                          //to a vector that is filled with zero elements
    
    链接地址: http://www.djcxy.com/p/67788.html

    上一篇: std :: array的默认初始化?

    下一篇: 为什么列表初始化(使用大括号)比替代方法更好?