☑ C++11: Initialization

15 Jul 2013 at 1:38PM in Software
 |  | 

I’ve finally started to look into the new features in C++11 and I thought it would be useful to jot down the highlights, for myself or anyone else who’s curious. Since there’s a lot of ground to cover, I’m going to look at each item in its own post — this one covers changes to initialization of variables.

This is the 2nd of the 8 articles that currently make up the “C++11 Features” series.

child map

Following on from my previous post on C++11’s new features, today I’m looking at a couple of changes to the way initialization works.

Extended initializer lists

As an extension to C, C++03 supports initialization of arrays and structures by listing values in curly brackets, even allowing such definitions to be nested:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct RGB {
  unsigned char red;
  unsigned char green;
  unsigned char blue;
};

struct Pixel {
  int x;
  int y;
  struct RGB colour;
};

Pixel pixelArray[] = {{0, 0, {0xff, 0xff, 0xff}},
                      {1, 0, {0xff, 0x00, 0x00}},
                      /* ... */
                     };

Since there’s no difference between a class and struct in C++ except for the default access specifier being public in a struct and private in a class, this applies equally to both. However, in C++03 it’s only permitted if the type is POD1 — in C++11 it has been extended to cover all class types.

The following example demonstrates the difference:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>

struct PODClass
{
  int member1;
  float member2;
  char member3;
};

class NonPODClass
{
 public:
  NonPODClass(int arg1, float arg2);
  virtual void method();

 private:
  int member1;
  float member2;
  char member3;
};

NonPODClass::NonPODClass(int arg1, float arg2)
    : member1(arg1), member2(arg2), member3('x')
{
  std::cout << "NonPODClass constructed" << std::endl;
}

void NonPODClass::method()
{
  // ...
}

int main()
{
  PODClass podInstance = {1, 2.3, 'a'};     // Valid in C++03 and C++11
  NonPODClass cpp03InitStyle(2, 4.6);       // Valid in C++03 and C++11
  NonPODClass cpp11InitStyle = {3, 6.9};    // Only valid in C++11
  // ...
  return 0;
}

So far it doesn’t seem too much of a stretch, just a fancy way of calling a constructor. It’s also possible to override the constructor called when an initializer list is provided, however, by providing a constructor taking a std::initializer_list<>. For example, consider this class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <cstddef>
#include <initializer_list>
#include <stdexcept>

class MyIntArray
{
 public:
  MyIntArray(std::initializer_list<int> values);
  ~MyIntArray();
  int operator[](size_t index);

 private:
  size_t size;
  int *array;
};

MyIntArray::MyIntArray(std::initializer_list<int> values)
    : size(values.size()), array(NULL)
{
  array = new int[values.size()];
  int *p = array;
  for (std::initializer_list<int>::iterator it = values.begin();
       it != values.end(); ++it) {
    *(p++) = *it;
  }
}

MyIntArray::~MyIntArray()
{
  delete array;
}

int MyIntArray::operator[](size_t index)
{
  if (index >= size) {
    throw std::out_of_range("index invalid");
  }
  return array[index];
}

The following main() function shows how it could be used:

1
2
3
4
5
6
7
8
#include <iostream>

int main()
{
  MyIntArray fib = {1, 1, 2, 3, 5, 8, 13, 21};
  std::cout << "5th element: " << fib[4] << std::endl;
  return 0;
}

All the STL container types have also been updated to support this form of initialization.

It turns out that std::initializer_list<> is just a standard type but you can only construct one using the curly-bracket syntax — after that, however, it can be copied, passed into functions and otherwise manipulated. The list itself is not mutable after creation, however. For example, the main() function above could be modified to read:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <iostream>

int main(int argc, char *argv[])
{
  std::initializer_list<int> init;
  if (argc > 1) {
    init = {1, 1, 2, 3, 5, 8, 13, 21};
  } else {
    init = {1, 1, 2, 6, 24, 120, 720, 5040};
  }
  MyIntArray fib = init;
  std::cout << "5th element: " << fib[4] << std::endl;
  return 0;
}

Uniform initialization

Somewhat related to extended initializer lists are some changes to ensure that all objects may be consistently initialised with the same syntax regardless of their type. One example of where this can fall down in C++03 is the most vexing parse rule and it’s also convenient to have a constructor-like syntax which works with POD types even though they don’t have a defined constructor.

In C++11 the initializer list syntax can be used like a constructor for any type by putting braces in place of brackets, like so:

1
MyIntVector instance{1, 2, 3, 4};

When used with POD types this will work like an initializer list and when used with other class types it will invoke the appropriate constructor. The standard form of constructor invocation with round brackets is still sometimes required if a constructor taking std::initializer_list<> is defined and an alternate constructor needs to be called.

Perhaps these initialization changes aren’t all that sweeping, but it’s nice to see some more consistent behaviour between simple and complex types, and I suspect that these changes may be particularly useful when defining a class which may be templated on both POD and non-POD types.


  1. Plain Old Data: a class with no constructors and no non-public data members. In fact this is a simplification — see this SO answer for more details. Note that this covers C++03, the definition of POD changed a little in C++11 — I’ll cover that in a later post. 

The next article in the “C++11 Features” series is C++11: Type inference and constant expressions
Wed 4 Sep, 2013
15 Jul 2013 at 1:38PM in Software
 |  |