Home > Articles > Programming > C/C++

  • Print
  • + Share This
This chapter is from the book

18.2 Initialization

Consider our vector as it was at the end of Chapter 17:

class vector {
      int sz;                  // the size
      double* elem;            // a pointer to the elements
public:
    vector(int s)                                                       // constructor
         :sz{s}, elem{new double[s]} { /* . . . */ }                   // allocates memory
     ~vector()                                                        // destructor
         { delete[] elem; }                                         // deallocates memory
     // . . .
};

That’s fine, but what if we want to initialize a vector to a set of values that are not defaults? For example:

vector v1 = {1.2, 7.89, 12.34 };

We can do that, and it is much better than initializing to default values and then assigning the values we really want:

vector v2(2);           // tedious and error-prone
v2[0] = 1.2;
v2[1] = 7.89;
v2[2] = 12.34;

Compared to v1, the “initialization” of v2 is tedious and error-prone (we deliberately got the number of elements wrong in that code fragment). Using push_back() can save us from mentioning the size:

vector v3;                            // tedious and repetitive
v2.push_back(1.2);
v2.push_back(7.89);
v2.push_back(12.34);

But this is still repetitive, so how do we write a constructor that accepts an initializer list as its argument? A { }-delimited list of elements of type T is presented to the programmer as an object of the standard library type initializer_list<T>, a list of Ts, so we can write

class vector {
      int sz;                // the size
      double* elem;          // a pointer to the elements
public:
      vector(int s)          // constructor (s is the element count)
           :sz{s}, elem{new double[sz]}     // uninitialized memory for elements
    {
          for (int i = 0; i<sz; ++i) elem[i] = 0.0;  // initialize
    }
     vector(initializer_list<double> lst)            // initializer-list constructor
           :sz{lst.size()}, elem{new double[sz]}     // uninitialized memory
                                                     // for elements
          {
          copy( lst.begin(),lst.end(),elem);    // initialize (using std::copy(); §B.5.2)
          }
          // . . .
    };

We used the standard library copy algorithm (§B.5.2). It copies a sequence of elements specified by its first two arguments (here, the beginning and the end of the initializer_list) to a sequence of elements starting with its third argument (here, the vector’s elements starting at elem).

Now we can write

vector v1 = {1,2,3};       // three elements 1.0, 2.0, 3.0
vector v2(3);            // three elements each with the (default) value 0.0

Note how we use ( ) for an element count and { } for element lists. We need a notation to distinguish them. For example:

vector v1 {3};         // one element with the value 3.0
vector v2(3);         // three elements each with the (default) value 0.0
round-b.jpg

This is not very elegant, but it is effective. If there is a choice, the compiler will interpret a value in a { } list as an element value and pass it to the initializer-list constructor as an element of an initializer_list.

In most cases — including all cases we will encounter in this book — the = before an { } initializer list is optional, so we can write

vector v11 = {1,2,3};      // three elements 1.0, 2.0, 3.0
vector v12 {1,2,3};        // three elements 1.0, 2.0, 3.0

The difference is purely one of style.

Note that we pass initializer_list<double> by value. That was deliberate and required by the language rules: an initializer_list is simply a handle to elements allocated “elsewhere” (see §B.6.4).

  • + Share This
  • 🔖 Save To Your Account