3

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?

Slava
  • 43,454
  • 1
  • 47
  • 90

2 Answers2

4

Initializing objects with list initialization should be preferred wherever applicable, because:

  1. Among other benefits list-initialization limits the allowed implicit narrowing conversions.

    In particular it prohibits:

    • conversion from a floating-point type to an integer type
    • conversion from a long double to double or to float and conversion from double to float, except where the source is a constant expression and overflow does not occur.
    • conversion from an integer type to a floating-point type, except where the source is a constant expression whose value can be stored exactly in the target type.
    • conversion from integer or unscoped enumeration type to integer type that cannot represent all values of the original, except where source is a constant expression whose value can be stored exactly in the target type.
  2. Another benefit is that is immune to most vexing parse.
  3. Also, initialization list constructors are preferred over other available constructors, except for the default.
  4. Also, they're widely available, all STL containers have initialization list constructors.

Now concerning your example, I would say with knowledge comes power. There's a specific constructor for making a vector of 5 ones (i.e., std::vector<int> vi( 5, 1);).

101010
  • 41,839
  • 11
  • 94
  • 168
  • 2
    And it doesn't bother you that `std::vector vi( 5, 1);` looks almost exactly like `std::vector vi{ 5, 1};` but does something very different? There are enough pitfalls with brace-initialization that I think it's not helpful to list only its advantages. – Kyle Strand Oct 30 '15 at 22:39
  • @KyleStrand With a knife you can cut bread, you can also cut your fingers in the process, so learn how to properly use a knife. Every feature has its advantages and its disadvantages. IMHO using brace initialization has more advantages than disadvantages. – 101010 Oct 30 '15 at 23:10
  • 1
    [Clearly](https://www.youtube.com/watch?v=pWdd6_ZxX8c). But even if we take your analogy at face-value (and I'm not convinced any modern language should be as "knife-like" as C++ is, let alone as confusing--knives are simple!), in order to use a knife properly without hurting yourself, you must understand the risks as well as the benefits. Your answer explains only the benefits--and there are real risks. – Kyle Strand Oct 30 '15 at 23:13
  • And my comment about the two initializations *looking* very similar is more than just aesthetics. As Joel Spolsky says, [wrong code should look wrong](http://www.joelonsoftware.com/articles/Wrong.html). If two pieces of code *look* very similar but *do* very different things, that's somewhat dangerous from a language design (and usage) perspective. – Kyle Strand Oct 30 '15 at 23:15
  • @KyleStrand the looks of something is in most cases subjective. For example is it good to use `new` when you have smart pointers? – 101010 Oct 30 '15 at 23:25
  • IMHO the best rule of thumb is to never use braces when planning to call an actual constructor, excepting only the default constructor. Braces are good for primitives, default/value initialization, and list initialization. If you're calling a non-default constructor of a non-trivial type, use parentheses. (Edit: my sloppy "actual constructor" means "constructors that don't take an initialization list). – Nir Friedman Oct 30 '15 at 23:55
  • @101010 By "looks" I mean how the text of the code physically looks. It's not really "subjective" whether those two lines of code look similar; they do look similar. It is subjective whether or not that's a problem. As for `new`, you need to understand the dangers *as well as* the benefits in order to use it well--just as with brace initialization. (And no, I personally do not use `new` where I can avoid it with `make_unique`.) – Kyle Strand Oct 31 '15 at 01:13
  • By the way, here's an interesting (minor) benefit (in some cases): http://stackoverflow.com/a/33681284/1858225 – Kyle Strand Nov 13 '15 at 00:17
4

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).

Community
  • 1
  • 1
Kyle Strand
  • 15,941
  • 8
  • 72
  • 167
  • `auto i{1,2};` is ill-formed in C++17, and that change is a DR, so you should see it in C++11/14 modes as well. – T.C. Oct 30 '15 at 22:42
  • @T.C. DR meaning "defect report"? I'm not sure what that means--is it essentially a retroactive change to previous versions of the standard? If so, I didn't realize such a thing existed. Do you happen to know of a good write-up somewhere on the behavior of `auto` that includes that information? I'd rather link to some other resource than attempt a complete/correct explanation when my main point is just "it's complicated and confusing." – Kyle Strand Oct 30 '15 at 22:55