Sunday, February 24, 2008

Succinct Initialization of Arrays of Objects in C++

Whenever a system is changed, particularly with the intent to add substantial new functionality, previous methods of doing things do not always survive the transition. While often the new system provides better ways of doing things, it cannot be mastered all at once, and during the transition period it is terribly annoying to no longer be able to use a mature technique even though eventually there will no longer be a need to. This is the case with the transition from C to C++.

This post considers a specific example, the challenge of initializing arrays of objects in C++.

In C one could do this...

struct sD2 {
   int x;
   int y;
};

sD2 sd2[][3] = { {0,0}, {0,1}, {0,2} },
                 {1,0}, {1,1}, {1,2} },
               };

It was concise, clear, and easy to expand. Note the unused last comma, so cutting & pasting a new row is flawless.

Initializing arrays of objects is more awkward because a constructor must be called explicitly. The closest I’ve found in C++ is the following...

class withLongName {
public:
   int x;
   int y;
   withLongName () {} // Default ctor so withLongName a[5]; compiles.
   withLongName (int x_, int y_) : x(x_), y(y_) {} // 2-arg ctor.
};

#define withLongName C
withLongName wlnc[][3] = { { C(0,0), C(0,1), C(0,2) },
                           { C(1,0), C(1,1), C(1,2) },
                         };
#undef C

Without the defines, the table easily expands past the window width and adds typographic clutter to the initialization...

withLongName lnc[][3] = { { withLongName(0,0), withLongName(0,1), withLongName(0,2) },
                          { withLongName(1,0), withLongName(1,1), withLongName(1,2) },
                        };

One could use ‘D’ for data, or ‘I’ for initialization, or others, but I think I like ‘C’ for constructor. I don’t care for the extra preprocessor lines, but if one specs a struct to initialize, then respecs it as the body of the class, and loads the object from the struct, one has both duplicated info, separation of variables from their initialization values, and even more extra lines.

Robert Tadlock tested this strategy and found that it avoided the typical inefficiencies of calling both the default constructor and another one, or of creating temporary objects subsequently destroyed in the initialization process.

No comments: