71

To the best of my knowledge, there are three ways to initialize a variable in C++.

int x = 0;    // C-like initialization
int x (0);    // Constructor initialization
int x {0};    // Uniform initialization

The uniform initialization was brought on for C++11 to provide a more uniform syntax for initializing different types of variables, which required different syntax in C++03.

What are the differences between C-like, constructor, and uniform initialization? And should I always use the uniform initialization?

Jonas Stein
  • 6,826
  • 7
  • 40
  • 72
dayuloli
  • 16,205
  • 16
  • 71
  • 126
  • What do you mean by difference? For a basic data type like int, they are exactly the same. I would use "C-like" for int as that is the most intuitive way. – Neil Kirk Jul 25 '14 at 10:39
  • I am new to C++ but I'm thinking along the lines of what's happening behind the hood. I'm reading on it, but I assume the constructor initialization calls a constructor method in an object. What happens in that constructor? I also heard that the uniform initialization method will fallback to the constructor initialization in some instances. – dayuloli Jul 25 '14 at 10:42

2 Answers2

76

First, I would recommend looking at the following talk by Herb Sutter, in which he gives some recommendations about the subject. The brace-initialization discussion starts at around 23:00.

When you are talking about primitive data types, all 3 yield the same result. I personally prefer sticking with the old int x = 0 syntax, but it comes down to personal preference.

For class types, brace initialization and old-school constructor initialization are not completely interchangeable. For example:

vector<int> v (100); // Creates a 100-element vector
vector<int> v {100}; // Creates a 1-element vector, holding the value 100.

This is because std::vector has a constructor that explicitly defines std::initializer_list as its only argument. Keep in mind that

auto var = {1, 2};

creates a std::initializer_list, with var as its identifier.

The thing about initializer lists is that they provide consistency that is a welcome change from what was available beforehand. For example, if you were to initialize an array in C++, you would use:

int arr[] = {1, 2, 3, 4};

But, if you wanted to initialize a vector<int> with the same elements, you either had to:

  1. Initialize the above arr first and then pass arr and arr + 4
  2. Create the vector and push_back() the elements individually or in a loop.

With C++11, you could just use

vector<int> v = {1, 2, 3, 4}; // Same syntax. Nice! Note that the = is optional

Another instance in which brace initialization is helpful is that it provides a workaround to C++'s most vexing parse. From the talk, assume that we have two classes, origin and extents, whose instances can be passed to construct another object of type rectangle. The following statement:

rectangle w(origin(), extents());

doesn't allow you to create a rectangle object using origin and extents temporaries, because that statement is parsed as a function declaration. Tsk tsk. So normally, you would have to do:

origin  o;
extents e;
rectangle w(o, e);

With brace initialization, you can create them on the fly, and

rectangle w {origin(), extents()};

will work as intended, i.e. passed to the constructor which is overloaded with an origin object as it's first argument and an extents object as the second.

The rule is for objects, use brace initialiation unless you have a reason not to.

Nasser Al-Shawwa
  • 3,573
  • 17
  • 27
  • 3
    I still don't get it. As I read somewhere, C++ will interpret it as a function declaration if it looks like one. Both `rectangle w(origin(), extents());` and `rectangle w(o, e);` look like function declarations, what is the difference between them? – SexyBeast Jan 14 '15 at 20:18
  • 3
    @Cupidvogel the second example is not a function declaration, because you do not have the types of the parameters there. `o` and `e` are instances of `origin` and `extents`, respectively. – Nasser Al-Shawwa Jan 15 '15 at 10:15
  • Yes, but the first one is also similar, right? `o` is actually `origin()`, so both don't have types, right? – SexyBeast Jan 15 '15 at 10:23
  • 6
    Not quite. the `origin()` in `rectangle w(origin(), extents())` is parsed as a pointer to a function that takes no arguments and returns an `origin` object. Same with `extents()`. So they are types. – Nasser Al-Shawwa Jan 15 '15 at 11:37
  • 3
    *"[...] about primitive data types, all 3 yield the same result."* - This is not 100% true since brace-initialization do not allow narrowing conversions while the others does (`char c(999);` is valid while `char c{999};` is not for 8-bits `char`). You also do not mention that `T x = ...` performs a copy-initialization (that, I agree, most compilers would elide). – Holt Jun 29 '16 at 07:22
  • Agreed with Holt, there are nuances missing here, especially that of copy-initialisation. Also: In C++17, `auto thing{1}` will deduce an `int`, `auto thing = {1}` or `= {1, 2}` an `std::initializer_list`, and `auto thing{1, 2}` is disallowed. This has been done so that uniform initialisation can be used with `auto` in a more intuitive way. GCC already does this for C++ <17, but Clang doesn't. Paper: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3922.html – underscore_d Jun 29 '16 at 07:23
  • Since brace-initialization and direct-initialization have different semantics in the container-like case, it’s quite reasonable to say that one should use the former for, and (aside from tricks like working around the most vexing parse) only for, expressing **assembly** from components rather than **computation** (of a class-type value) from parameters. Even `T()` and `T{}` differ in certain aggregate cases, so the only general advice is “use the one you mean”. – Davis Herring Nov 29 '20 at 17:23
  • @nasser-sh but then, what if there are types named `o` and `e` ? Does C++ parsing depend on the names in scope? – Maëlan Jul 08 '21 at 16:46
18

What are the differences between c-like, constructor, and uniform initialization?

For primitive types like int, there's no practical difference; so let's consider a class type T instead.

The first style is equivalent to

T x(T(0));

creating a temporary object from the initialiser expression, and then initialising x by moving or copying that. In practice, the move or copy will be elided, so that the result is the same as the second style; the only difference is that the first will fail if there isn't an accessible copy or move constructor.

The second directly initialises the object using a constructor that takes one argument, giving an error if there's no suitable constructor.

The third depends on what constructors are available.

  • if there's a constructor taking std::initializer_list, it uses that;
  • otherwise, if there's a constructor taking a single argument of a suitable type, it uses that;
  • otherwise, if it's an aggregate (with no constructors) with one member, that member is initialised with zero;
  • otherwise, it's an error.

And should I always use the uniform initialization?

No. Sometimes you need function-style initialisation to distinguish between an initializer_list constructor and one taking other argument types. For example:

std::vector<int> v1(10, 42);  // 10 elements with value 42
std::vector<int> v2{10, 42};  // 2 elements with values 10 and 42

You also shouldn't call it "uniform initialisation" since it's not "uniform" in any meaningful sense. The official term is "brace-initialisation".

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • 1
    `T x = 0;` will also fail if the constructor `T(int)` is marked as explicit (which is a common thing for some standard containers!). – Holt Jun 29 '16 at 07:29
  • Did you make an error in this statement? "if it's an aggregate (with no constructors) with one member, that member is initialised with zero;" ? I don't get it – Patrick Parker Nov 21 '18 at 06:15
  • @PatrickParker I think he means, for example, if you have `struct Thing { int value };`, and you didn't manually write a constructor for it, then writing `Thing t{0}` will construct a `t` of type `Thing`, whose only member `int value` will be initialised to `0`. @MikeSeymour correct me if I'm wrong. (I'm no expert in C++.) – Ray Nov 21 '20 at 12:13