Saturday, May 24, 2014

Initializing a C++ container with an initializer list, without C++11

This is a common scenario when programming C++. You want to do something that your instincts tell you should be possible, and yet it doesn't work. A quick Google search turns up several answers on "Stack Overflow." However, the answer is almost invariably the same: "Just compile with C++11 enabled."

Well, if you're like me, you seldom have complete control over the restraints for the project you're working on. For example, you may be developing for RHEL 6, and are required by your consumer to use the default GCC compiler version, which is 4.4. Support for C++11 is far from complete in GCC 4.4. Or you may simply not be allowed to use C++11. After all, GCC still lists C++11 support as "experimental."

I've found this particularly frustrating when initializing C++ containers, such as maps or vectors. Pre-C++11, they have to be initialized one element at a time.This can be very inconvenient, and make code look quite messy.

For instance, you may need to keep a table of values, that's initialized at start-up, and remains constant throughout program execution. The preferred way to do this, in my opinion, is to put the table in an initializer list. This can be contained in a source file, where it's used in the declaration of a static global array. It can also be defined as a macro in a header file.  Personally, I like the second approach, because it keeps the source file from looking cluttered, especially for large tables.

But the problem is that, for C++98, this only works with arrays. The closest you're going to get is to first initialize an array with the initializer list, and then use the array to construct your C++ container.

Here's an example that stores a set of tables in a vector. The tables are used to load serial port configuration parameters from a Json file. The initializer lists for the tables are defined as a macros in a header file. LookupTable is a template class used for creating read-only one-to-one maps. Internal storage for LookupTable is handled by a std::map.

#typedef LookupTable<string, unsigned> TermiosFlagMap;

    TermiosFlagMap::mapping temp[] = INITLIST; \
    maplen = sizeof(temp) / sizeof(TermiosFlagMap::mapping); \
    data.push_back (TermiosFlagMap (temp, temp+maplen)); \

class TermiosFlagMapMaster: boost::noncopyable
    std::vector<TermiosFlagMap> data;
        unsigned maplen;


    TermiosFlagMap& operator[] (SerialPort::eFlagType type)
        return this->data[type];
} static FLAGMAPS;
#undef MAPINIT

Note that a macro function is used to avoid having to repeat the same lines of code for each table. This deserves an explanation.

A macro can be used to enclose repetitive code that can't be made into a function or an iterative loop. By enclosing the macro definition in a code block (using curly-braces), you get similar behavior to a function, such as restricted scope and variable re-initialization.

For example, you can use it to iteratively perform an operation that requires multiple definitions of an array. Enclosing the macro in a block, causes the array to go out of scope after each iteration, thus allowing it to be redefined and initialized the next time around. The macro argument can be an initializer list, itself a macro. You couldn't do this with a function, because there'd be no way to pass the initializer list to it.

This is useful for initializing C++ containers from initializer lists, even when your compiler doesn't support C++11. You'll typically want to use arrays defined locally in a function, so that their memory will be released when the function goes out of scope. Of course, creating an array, and then copying each element to a container can be expensive. However, in this case the initialization only occurs once at program start-up, and so this isn't so important. 

No comments:

Post a Comment