|
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? For
example:
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 expression above is a
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,
in which 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[42]:
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 of 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 (which is typically the case for a well-designed class in C++),
or even if everything’s public but there’s a constructor,
things are different. In the examples above, 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);
};
You must indicate constructor calls. The
best approach is the explicit one as follows:
Y y1[] = { 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
#include <iostream>
using namespace std;
class Z {
int i, j;
public:
Z(int ii, int jj);
void print();
};
Z::Z(int ii, int jj) {
i = ii;
j = jj;
}
void Z::print() {
cout << "i = " << i << ", j = " << j << endl;
}
int main() {
Z zz[] = { Z(1,2), Z(3,4), Z(5,6), Z(7,8) };
for(int i = 0; i < sizeof zz / sizeof *zz; i++)
zz[i].print();
} ///:~
Notice that it looks like an explicit
constructor is called for each object in the
array.
|
|