In C++, initializing objects is not done in the same manner in all contexts. The rules for initialization and constructors are not really simple and developers sometimes have to write lot of explicit code for initializing objects. Fortunately the new C++ standard comes to the rescue, offering a uniform initialization syntax and semantics.
The way it was before C++ 2011
Let’s take the following structure as a start point.
struct Color { unsigned char A; unsigned char R; unsigned char G; unsigned char B; };
If you want to initialize an object of this type, then you can write:
Color c; c.A = 0xff; c.R = 0xff; c.G = 0x00; c.B = 0x00;
But there is a simpler way to write that:
Color c = {0xff, 0xff, 0x00, 0x00};
If you want to zero the entire structure, then you can write:
Color c = {0};
However, you cannot use this initialization type to initialize all the members with 0xff, for instance. If you write
Color c = {0xff};
then only A will be 0xff, the other fields would be initialized with a default value, which is 0.
Since this is a POD (Plain Old Data) type (which is a scalar or a POD class – a class with no user defined constructors or destructor, no user defined copy assignment operator, no base classes or virtual functions, no private or protected non-static data and no static data that is non-POD types) you could also write:
memset(&c, 0, sizeof(c));
Mind you, initializing with 0 with memset works for any POD type, however, initializing with another value, say 1, will not have the expected result; if A, R, G and B would have been ints for instance, because memset puts the specified value in every byte starting at the specified address for the specified count, you’d get ints that are 0x01010101 instead of simply 1.
When it comes to array you can initialize all the elements of the array with 0 as in:
Color ca[2] = {0};
which is identical to
Color ca[2] = {{0}, {0}};
However, if you want to initialize with something other than 0, then you can write:
Color ca[2] = {{0}, {0xff, 0xff, 0x00, 0x00}};
Now, another way to initialize Color would be to define a constructor (which makes the type non-POD, so no more memset). The default value of the constructor parameters allow you to declare arrays of this object. Otherwise, since there is no default constructor, it wouldn’t be possible to declare an array of Color.
struct Color { unsigned char A; unsigned char R; unsigned char G; unsigned char B; Color( unsigned char a = 0, unsigned char r = 0, unsigned char g = 0, unsigned char b = 0): A(a), R(r), G(g), B(b) {} };
In this case you can say:
Color c(0xff, 0xff, 0x00, 0x00);
However, since Color is no longer an aggregate (because it has a user defined constructor) this initialization is no longer supported; the following issues errors:
Color c = {0xff, 0xff, 0x00, 0x00}; Color ca[2] = {{0}, {0xff, 0xff, 0x00, 0x00}};
If instead of using an array, we’d want to use a vector of Color, then initializing it would be only possible like this:
vector<Color> cv; cv.push_back(Color(0)); cv.push_back(Color(0xff, 0xff, 0x00, 0x00));
Or
vector<Color> ca(2); ca[1] = Color(0xff, 0xff, 0x00, 0x00);
Or
ca[1].A = 0xff; ca[1].R = 0xff; ca[1].G = 0x00; ca[1].B = 0x00;
Or if you’d have an array of Color, then you can say:
Color ca[2] = {Color(0), Color(0xff, 0xff, 0x00, 0x00)}; vector<Color> cv(ca, ca+2);
Enter C++ 2011
The new standard introduces a new way of initializing objects, in a more uniform manner. Even though Color is a non-POD type you can write:
Color c {0xff, 0xff, 0x00, 0x00}; Color ca[2] {{0}, {0xff, 0xff, 0x00, 0x00}}; vector<Color> ca {{0}, {0xff, 0xff, 0x00, 0x00}};
Or (these two being identical)
Color c = {0xff, 0xff, 0x00, 0x00}; Color ca[2] = {{0}, {0xff, 0xff, 0x00, 0x00}}; vector<Color> ca = {{0}, {0xff, 0xff, 0x00, 0x00}};
Or with a map:
map<string, Color> colors = { {"red", {0xff, 0xff, 0x00, 0x00}}, {"green", {0xff, 0x00, 0xff, 0x00}}, {"blue", {0xff, 0x00, 0x00, 0xff}}, };
It is important to note that the initialization with the parenthesis, as shown in the previous paragraph as the only way to initialize non-POD types before C++ 2011, is still available.
Also note that in the following code sample, the first vector will have three elements (initialized with the default value), while the second will have a single element with value 3.
vectorvec1(3); vector vec2{3};
If the array or the vector were members of a class, the following initialization could be done:
class foo { int data[3]; public: foo(): data{1,2,3} {} }; class bar { vectordata; public: bar(): data{1,2,3} {} };
What if the array would be dynamically allocated on the heap? Then, you can use the same initialization syntax with {}:
int* data = new int[3] {1,2,3};
Or, in case it is part of a class
class foobar { int* data; public: foobar(): data(new int[3]{1,2,3}){} };
It is also possible to use the curly braces to initialize an object in a return statement:
Color make_color(unsigned char r, unsigned char g, unsigned char b) { return {0xff, r, g, b}; }
Or for a function call:
void use_color(Color c) { } use_color({0xff, 0xff, 0x00, 0x00});
Conclusions
The new C++ standard brings a new, uniform initialization syntax and semantics, helping developers to write simpler and uniform code. Using the curly braces {} constructs the same value in all contexts where it is legal. Unfortunately, not all compilers support this new feature at the time of writing this article, but they will pretty soon.