Search code examples
c++c++11initializer-list

Is it good habit to always initialize objects with {}?


Initializing objects with new {} syntax like this:

int a { 123 };

has benefit - you would not declare a function instead of creating a variable by mistake. I even heard that it should be habit to always do that. But see what could happen:

// I want to create vector with 5 ones in it:
std::vector<int> vi{ 5, 1 }; // ups we have vector with 5 and 1.

Is this good habit then? Is there a way to avoid such problems?


Solution

  • Frankly, the subtleties of the various initialization techniques make it difficult to say that any one practice is a "good habit."

    As mentioned in a comment, Scott Meyers discusses brace-initialization at length in Modern Effective C++. He has made further comments on the matter on his blog, for instance here and here. In that second post, he finally says explicitly that he thinks the morass of C++ initialization vagaries is simply bad language design.

    As mentioned in 101010's answer, there are benefits to brace-initialization. The prevention of implicit narrowing is the main benefit, in my opinion. The "most vexing parse" issue is of course a genuine benefit, but it's paltry--it seems to me that in most cases an incorrect int a(); instead of int a; would probably be caught at compile time.

    But there are at least two major drawbacks:

    • In C++11 and C++14, auto always deduces std::initializer_list from a brace-initializer. In C++17, if there's only one element in the initialization list, and = is not used, auto deduces the type of that element; the behavior for multiple elements is unchanged (See the second blog post linked above for a clearer explanation, with examples.) (Edit: as pointed out by T.C. in a comment below, my understanding of the C++17 rules for auto with brace initialization is still not quite right.) All of these behaviors are somewhat surprising and (in mine and Scott Meyers' opinions) annoying and perplexing.
    • 101010's third listed benefit, which is that initialization list constructors are preferred to all other constructors, is actually a drawback as well as a benefit. You've already mentioned that the behavior of std::vector<int> vi{ 5, 1 }; is surprising to people familiar with vector's old two-element constructor. Scott Meyers lists some other examples in Effective Modern C++. Personally, I find this even worse than the auto deduction behavior (I generally only use auto with copy initialization, which makes the first issue fairly easy to avoid).

    EDIT: It turns out that stupid compiler-implementation decisions can sometimes be another reason to use brace-initialization (though really the #undef approach is probably more correct).