MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Aggregate initialization

An aggregate is just what it sounds like: a bunch of things clumped together. This definition includes aggregates of mixed types, like structs and classes. An array is an aggregate of a single type.

Initializing aggregates can be error-prone and tedious. C++ aggregate initialization makes it much safer. When you create an object that’s an aggregate, all you must do is make an assignment, and the initialization will be taken care of by the compiler. This assignment comes in several flavors, depending on the type of aggregate you’re dealing with, but in all cases the elements in the assignment must be surrounded by curly braces. For an array of built-in types this is quite simple:

int a[5] = { 1, 2, 3, 4, 5 };
If you try to give more initializers than there are array elements, the compiler gives an error message. But what happens if you give fewer initializers, such as

int b[6] = {0};

Here, the compiler will use the first initializer for the first array element, and then use zero for all the elements without initializers. Notice this initialization behavior doesn’t occur if you define an array without a list of initializers. So the above expression is a very succinct way to initialize an array to zero, without using a for loop, and without any possibility of an off-by-one error (Depending on the compiler, it may also be more efficient than the for loop.)

A second shorthand for arrays is automatic counting, where you let the compiler determine the size of the array based on the number of initializers:

int c[] = { 1, 2, 3, 4 };

Now if you decide to add another element to the array, you simply add another initializer. If you can set your code up so it needs to be changed in only one spot, you reduce the chance of errors during modification. But how do you determine the size of the array? The expression sizeof c / sizeof *c (size of the entire array divided by the size of the first element) does the trick in a way that doesn’t need to be changed if the array size changes:

for(int i = 0; i < sizeof c / sizeof *c; i++)
  c[i]++; 

Because structures are also aggregates, they can be initialized in a similar fashion. Because a C-style struct has all its members public, they can be assigned directly:

struct X {
  int i;
  float f;
  char c;
};

X x1 = { 1, 2.2, 'c' }; 

If you have an array of such objects, you can initialize them by using a nested set of curly braces for each object:

X x2[3] = { {1, 1.1, 'a'}, {2, 2.2, 'b'} };

Here, the third object is initialized to zero.

If any of the data members are private, or even if everything’s public but there’s a constructor, things are different. In the above examples, the initializers are assigned directly to the elements of the aggregate, but constructors are a way of forcing initialization to occur through a formal interface. Here, the constructors must be called to perform the initialization. So if you have a struct that looks like this,

struct Y {
  float f;
  int i;
  Y(int a); // Presumably assigned to i
};

You must indicate constructor calls. The best approach is the explicit one as follows:

Y y2[] = { Y(1), Y(2), Y(3) };

You get three objects and three constructor calls. Any time you have a constructor, whether it’s a struct with all members public or a class with private data members, all the initialization must go through the constructor, even if you’re using aggregate initialization.

Here’s a second example showing multiple constructor arguments:

//: C06:Multiarg.cpp
// Multiple constructor arguments
// with aggregate initialization

class X {
  int i, j;
public:
  X(int ii, int jj) {
    i = ii;
    j = jj;
  }
};

int main() {
  X xx[] = { X(1,2), X(3,4), X(5,6), X(7,8) };
} ///:~ 

Notice that it looks like an explicit but unnamed constructor is called for each object in the array.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru