Initialization forms in C++11

Recently I was learning C++ and I got to know the new brace initialization in C++11. Therefore, I decided to take a look at various initialization methods and the reason behind this new syntax. According to my research, in C++03, the initialization methods are a bit tricky and have certain restrictions. Let’s take a look at how you initialize objects in C++03 and how it could be improved with C++11.

Initialization in C++03

Initialization with equal sign

Equal sign can be used to initialize basic data types, for example

int a = 0;
double b = 0.0;

Initialization with parentheses

Equal sign can initialize basic data types, that’s cool. But if you want to initialize objects, you need to use paratheses. For example

class A
{
    private:
        int m_x, m_y;
    public:
        A(int x, int y) : m_x(x), m_y(y) {}
};

A a(1, 2);

This notation could also be used to initialize basic data types

int a(0); // this works too

Initialization with braces

Besides equal sign and parentheses, you could also use braces. These are used to initialize aggregates like array. Strings are kinda special and could use double quotes to initialize.

int arr[5] = { 0, 1, 2, 3, 4 };
char msg[] = "message"; // this is special

Restrictions

So we had a brief overview of object initialization in C++03, what are the restrictions? First, you cannot initialize member arrays.

class A
{
    int mem_arr[5]; // you cannot initialize mem_arr
    A() {}
};

Second, you cannot initialize STL containers like vector<T>, you need to use things like std::vector<T>::push_back().

vector<int> int_vec();
int_vec.push_back(0);
int_vec.push_back(1);

Also, you cannot initialize dynamically allocated arrays.

int *arr = new int[10];

Now you are aware of the restrictions and various initialization notations in C++03, let’s see what could be improved with C++11.

Brace initialization in C++11

In C++11, a “universal” initialization syntax with braces was introduced. You could use the braces to replace all the notations you see above in C++03

int a{0}; // initialization of basic data type
A a{1, 2}; // initialization of object
int arr[5]{0, 1, 2, 3, 4}; // initialization of aggregates

And it overcomes the restrictions in C++03

// initialize member array
class A
{
    int mem_arr[5];
    A() : mem_arr{0, 1, 2, 3, 4} {}
};

// initialize STL container
vector<int> inv_vec{0, 1};

// initialize dynamically allocated array
int *arr = new int[5]{0, 1, 2, 3, 4};

Initializer list

You may wonder how this new notation is implemented in C++11. Basically, a new template class std::initializer_list<T> was introduced to pass a list of values to the constructor. Let’s see how we could use this to initialize aggregates.

#include <initializer_list>

class A
{
    private:
        // member variables
    public:
        A(std::initializer_list<int> numbers)
        {
            // initialize member variables
        }
};

A a{0, 1, 2, 3}; // matched to A(initializer_list<int> numbers)

New limitation

With this new notation, you may think you could finally have an universal way of initializing objects in C++. Sadly, this is not true. Notice that the compiler will first try to match a std::initializer_list<T> constructor before matching a normal constructor

#include <initializer_list>
#include <iostream>

Class A
{
    private:
        int a;
    public:
        A(std::initializer_list<int> numbers)
        {
            std::cout << "initializer list" << std::endl;
        }
        A(int number)
        {
            std::cout << "normal constructor" << std::endl;
        }
}

A a{1}; // prints "initializer list"
A a(1); // prints "normal constructor"

Therefore, if you want to initialize an std::vector<int> of size 1, you cannot use the new initialization form because it will create a vector of size 1 and initialize its value to 1. You still need to use parentheses if you want to do that.

#include <iostream>
#include <vector>

int main()
{
  std::vector<int> vec_a{1};
  std::cout << vec_a.at(0) << std::endl; // prints 1
  
  std::vector<int> vec_b(1);
  std::cout << vec_b.at(0) << std::endl; // prints 0
}

If C++ is designed so that the compiler will only match the std::initializer_list<T> constructor when you use the form std::vector<int> a{{1}}; (double braces), the above situation won’t happen.

Conclusion

This post discussed and compared various initialization methods in C++03 and C++11. As we can see, C++11 wants to unify the way we initialize all kinds of objects with the new brace initialization. However, as it solves old problems, it also introduces new problems to the table. Personally, I would say this is a nice attempt but it could be done better. The take-away is that you still need to be careful when you initialize objects in C++ in order for your programs to behave as desired. The new initialization form cannot help you with that in all situations.

 
comments powered by Disqus