Understanding X Macros in C++: A Legacy Technique Explored
Written on
Chapter 1: The Dilemma of Using Macros
Utilizing macros in C++ isn't generally considered best practice, as they stem from C preprocessor techniques that are often better avoided. However, we frequently encounter legacy code that necessitates an understanding of various methods essential for writing safe and maintainable code.
A notable technique in this context is the use of X macros. This approach enables developers to create a mapping between an enumeration and its corresponding string in a single declaration, thereby simplifying updates and reducing errors. It ensures that changes are made in one central location, minimizing the risk of omitting updates in either the enumeration or the associated string array.
To explore how this technique functions, let’s take a look at the following example.
#include <iostream> // For std::cout
#include <string> // For std::string
#include <typeinfo> // For typeid
#define SHAPE_MAP
MAP(CIRCLE, "circle")
MAP(RECTANGLE, "rectangle")
MAP(TRIANGLE, "triangle")
#define MAP(a, b) a,
enum SHAPE {
SHAPE_MAP
MAX_SHAPE
};
#undef MAP
#define MAP(a, b) b,
std::string shape_name[] = {
SHAPE_MAP
""
};
#undef MAP
int main() {
for (unsigned int i = CIRCLE; i < MAX_SHAPE; ++i) {
std::cout << "shape index: " << i <<
" shape name: " << shape_name[i] <<
std::endl;
}
return EXIT_SUCCESS;
}
The output of the above code provides the corresponding shape names based on their indices.
Section 1.1: Understanding the Code
Let's dissect the code step by step to understand its functionality. Initially, we define the SHAPE_MAP macro, which internally uses the MAP macro. The MAP macro is defined to take two parameters, a and b, and replaces calls with a,. The comma is crucial, as it separates the enumeration values.
When the SHAPE_MAP macro is executed, the preprocessor acknowledges the definition of the MAP macro, replacing it accordingly. After populating the SHAPE enumeration, the MAP macro is undefined, allowing for its reuse later in the string array definition.
In this second instance, the MAP macro again takes a and b, but this time it returns b,, which is necessary to define the shape names using SHAPE_MAP.
The end result is two synchronized data structures: an enumeration (enum SHAPE) and a string array (shape_name[]), effectively creating a mapping between the two. This technique is incredibly useful for C developers and often appears in legacy C++ code.