For those who have not used C or C++ I’ll start with a brief explanation: There is a thing called the pre-processor, and it is run over source files before they are compiled. It does a bunch of simple and useful things like pulling header files into source files for info about library functions. Take a look at a tutorial C program, the first line will probably look like this:
#include <stdio.h>
That include line is a preprocessor macro, and it gets information about functions in a library somewhere else. I will be discussing here more complex macros, and their uses and pitfalls.
So I’ve Got a Bunch of Things
Repetition is something which, in programming, should be avoided at all cost. That is, programs will do repetitive tasks all the time, but programmers should avoid doing repetitive tasks like the plague. Most of the time there is a non-repetitive solution which might cost time in the short term, but it’ll save time for the rest of forever. Sometimes it might seem like this is unavoidable: you need a bunch of classes called “Foo”, “Bar”, and “Baz”, but they don’t really do anything important, they just initialise a base class with some data. These classes might look like this:
class Base {
public:
int x, y, z;
};
class Foo : public Base{
public:
Foo() : x(0), y(1), z(50) {}
};
class Bar : public Base {
public:
Bar() : x(3), y(100), z(3) {}
};
class Baz : public Base {
public:
Baz() : x(-6), y(123), z(0xdeadbeef) {}
};
This is repetitive, and a waste of time to write. In the Sim/Management game I’m currently working on, it’s worse still and I’ve got an enum of the names of the base classes too. So rather than waste time writing this out, I set up a pre-processor macro to do it for me.
Pre-processor macros usually look like:
#define SOMETHING Something(); if(Maybe()){ LotsOfStuff();}
but they can also take arguments. And importantly the arguments can be other macros, including other macros which take arguments. In the above list of class definitions there are lots of sections where the only thing that changes is the name (and some numbers, but we’ll get to that). Now that means the code could be replaced by a macro taking that name as an argument. But we can also supply those arguments using a macro if we will be using the same list of arguments frequently!
Here We Go: The Argument List Macro
#define ALLTHETHINGS(f)\
f(Foo)\
f(Bar)\
f(Baz)
To explain, what this does, is given an argument f, it treats f like a function and calls it with Foo, Bar, and Baz as arguments. Note that the preprocessor acts on source code, these are not strings but typeless text, whatever the function of the text is after the macro is processed is what it does – it can be string data, int constants, keywords, or anything else.
An easy example of how this can be used would be to make an enum with these names, this would be most easily achieved by writing the following.
enum Names {
# define ENAMES(op) op,
ALLTHETHINGS(ENAMES)
# undef ENAMES
};
Voila! To add an entry to the enum, just add it to the ALLTHETHINGS macro, and it’ll exist. The reason for undefining the ENAMES macro after its use is we don’t want to leave defined macros lying around where they could cause problems.
That’s no great saving, adding the entry in one place to save adding it in one place, but let’s look again – our definition of the classes above could be made into a macro as well. We will have to make a few changes however, the simplest being not defining the constructor in the class definition, so we only need a macro like:
#define CLASSTYPE(op) \
class op : public Base { \
public: \
op(); \
};
And we can then use it with the ALLTHETHINGS macro as above. To sort out those constructors we will unfortunately have to repeat ourselves a little. The macro I’ll describe shortly requires the name of the class, as well as the value of the initialisers, it goes like this:
#define CONSTRUCTOR(Type, X, Y, Z)\
Type::##Type() \
: x(X), y(Y), z(Z) {}
You can then call this as (for example) CONSTRUCTOR(Foo, 0, 1, 50) and it will create that constructor definition for you.
MACROISE ALL THE THINGS
So what’s the downside? If you get it wrong, the error messages are not going to be very helpful. They’ll point to the line which generated the error, but you could have a one line composite macro produce literally thousands of lines after the pre-processor has done its thing. Depending on the mistake you make that could produce thousands of errors as well. The other problem which appears if you work in a nice and friendly IDE, or at least work in Visual Studio, is that code completion is not going to understand what you’ve done. Intellisense will tell you that these classes do not exist and all references to them are errors. Even in Visual Studio Pro 2010, it marks macros inside enums as erroneous.
There is a solution which addresses both of these problems, but I believe has it’s own issues – mainly adding a build step. You could write a small program to expand the macros for you, while not expanding the #include calls. This would mean that the resultant header file would be easy for Intellisense (and non-microsoft equivalents) to parse, and the errors should in theory be easier to trace. Having IDEs call your program as part of the build process is certainly possible, though perhaps not as easy as writing it into a makefile (if you are that kind of developer). It also occurs to me that pre-compiled headers might be a solution, but I’ve never used them so I can’t comment.
I’m no expert on macros, the things I’ve listed above are approaches I’ve used (recently) in my own project. There are many more uses macros may be put to, and many other things the pre-processor can be persuaded to do for you (try looking up template meta-programming, for example) and even many things that they can do to make programming harder. RTFM, and use wisely.