Sunday 15 December 2013

Uniform Initialization in VC++ 2013

Before uniform initialization was introduced to C++ there were many ways to initialize variables. For example:

int myInt(3);
int myInt = 3;
Musician guitarist; // Zero argument constructor
Musician drummer("Black", 10.01, true);

Uniform initialization provides a uniform way to initialize objects using curly braces. For example:

int myInt {4};
Musician piper {}; // Zero argument constructor
Musician jazzFlautist {"Black", 12.34, true};

Uniform initialization provides a standard method for initializing objects and for consistency alone should be the first preference. However, there are further advantages.

Further advantages of Uniform Initialization

The uniform initialization syntax can be used with the class std::initializer_list<> to pass a variable number of similar arguments to a constructor. This is useful for creating std containers, for example:

vector<int> myInts { 1, 3, 2, 4};

will create a vector of ints with four elements: 1, 2, 3, and 4To make use of std::initializer_list<> in your own classes you'll need to write an initializer list constructor, i.e. a constructor that takes an std::initializer_list<> of the desired type and parses it as required. If you do this users will be able to use the { } notation to invoke that constructor.

Uniform initialization can also be nested, for example:

vector<Musician> band { guitarist ,
                        jazzFlautist,
                        {"Blue", 43.21, false} };

In the above case guitarist and jazzFlautist are added to the vector, as is a third anonymous object that is created and copied (or hopefully moved) into the vector.

Uniform initialization also brings a way to avoid Most Vexing Parse confusion. For a simple illustration consider the following code:

Musician harpist();

This may be considered an acceptable way to create a Musician object called harpist with the zero argument constructor, but in fact it declares a function called harpist that takes zero arguments and returns a Musician. This ambiguity can take more subtle forms, for example where a function call is intended as an input parameter to a function call, but the compiler reads this as a function declaration that takes a function type as a parameter. It's so called the most vexing parse for good reason, but it is avoided using by the curly braces uniform initialization notation, i.e.

Musician harpist{};

Another advantage of uniform initialization is that it is a convenient way to initialise Aggregate classes. For example given the following definition:

struct number {
int asInteger;
std::string asString;
bool isPrime;
};

a number can be initialized with:

number theNumberFour{ 4, string("four"), false };

Reasons to still use the old constructor syntax

There may still be times when you wish to use the old constructor syntax. If a class has an initializer list constructor then it will take precedence over other constructors. If you wish to call the other constructors you'll need to use the old constructor syntax to be explicit.

For example, if you wish to create a std::vector of ints and you wish to call the constructor that takes a single parameter specifying the vector's size then writing:

std::vector<int> myInts{4};

won't work. The above code would create a vector of ints and initialize it with the single element 4. Instead you need to use the round bracket notation :

std::vector<int> myInts(4);

However, in general you should default to using uniform initialization, and only break that pattern when you've got a specific reason.

No comments: