I recently came across a clever way of writing preprocessor macros, and I figured that I would share.
Let’s say that for some reason you need to write a macro: MACRO(X,Y). You want this macro to emulate a function call in every way.
Example 1: This should work as expected.
if (x > y)
MACRO(x, y);
do_something();
Example 2: This should not result in a compiler error.
if (x > y)
MACRO(x, y);
else
MACRO(y - x, x - y);
Example 3: This should not compile.
do_something();
MACRO(x, y)
do_something();
The naïve way to write the macro is like this:
#define MACRO(X,Y) \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl;
This is a very bad solution which fails all three examples, and I shouldn’t need to explain why.
Now, the way I most often see macros written is to enclose them in curly braces, like this:
#define MACRO(X,Y) \
{ \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl; \
}
This solves example 1, because the macro is in one statement block. But example 2 is broken because we put a semicolon after the call to the macro. This makes the compiler think the semicolon is a statement by itself, which means the else statement doesn’t correspond to any if statement! And lastly, example 3 compiles OK, even though there is no semicolon, because a code block doesn’t need a semicolon.
The solution is kind of clever, I thought:
#define MACRO(X,Y) \
do { \
cout << "1st arg is:" << (X) << endl; \
cout << "2nd arg is:" << (Y) << endl; \
cout << "Sum is:" << ((X)+(Y)) << endl; \
} while (0)
Now you have a single block-level statement, which must be followed by a semicolon. This behaves as expected and desired in all three examples. I have noticed this macro pattern before, but I never really thought about why it was written this way. Mainly because I don’t often write macros to begin with.
February 15, 9:31 pm
Granted, I’ve been out of the C++ world for a few years now, but why is your company:
1) Not using smart pointers
2) Not using STL containers
February 16, 12:12 pm
1. We do. Nearly everything I work with on a regular basis inherits from COM+, so we get smart pointers for free. I’m not sure why the guys who wrote the database-layer code (where ObjectID comes from) didn’t use them, but my guess is they wanted to make the code as lean as possible.
I used COM+ pointers (which you could make the case are also smart pointers) in my original code because I had to call an API that took a pointer. But since I took that call out in the simplified code, I could have just as easily written the for-loop like this:
for (int i = 1; i <= nbObjs; i++)
{
ObjectID* pObjId = NULL;
IPart_var hPart(listObjs[i]);
if (hPart != NULL_var)
RC = hPart->get_ObjectID(pObjId);
listObjIds[i-1] = pObjId;
}
But managing/freeing pointers wasn’t a problem here.
2. Well a LIST(DataType) is pretty similar to an STL vector. When all the rest of the code uses LISTs, using an STL vector wouldn’t be a good idea. That’d just lead to more bugs exactly like this one, when the next guy comes along and assumes the vector starts counting at 1 like everything else they’re used to working with. (That was the problem here, I was so used to base-1 lists that I forgot I was dealing with a plain-old C++ array.) If the LIST had been written to start at 0 to begin with this wouldn’t be a problem, but it’s far too late to change that now.