2

I had a debate about macros and their readability. I think that in some cases using macros can make the code shorter, more comprehensible and less tiring to read.

For example:

#include <iostream>

#define EXIT_ON_FAILURE(s) if(s != 0) {std::cout << "Exited on line " << __LINE__ << std::endl; exit(1);}

inline void exitOnFailure(int s, int lineNum) {
    if (s != 0) {
        std::cout << "Exited on line " << lineNum << std::endl; 
        exit(1);
    }
}

int foo() {
    return 1;
}

int bar(int a, int b, int c) {
    return 0;
}

int main() {
    // first option
    if (foo() != 0) {
        std::cout << "Exited on line " << __LINE__ << std::endl;
        exit(1);    
    }
    if (bar(1, 2, 3) != 0) {
        std::cout << "Exited on line " << __LINE__ << std::endl;
        exit(1);    
    }

    // second option
    EXIT_ON_FAILURE(foo());
    EXIT_ON_FAILURE(bar(1, 2, 3));

    // third option
    exitOnFailure(foo(), __LINE__);
    exitOnFailure(bar(1, 2, 3), __LINE__);

    return 0;
}

I prefer the second option here, since it's short and compact and the caps lock text is just clearer and easier to read than camel case.

Is there anything wrong with this method, particularly in C++, or is it just bad (but acceptable) style?

White Zebra
  • 309
  • 1
  • 5
  • 15
  • i think using macro is not good in most of the cases. third option is better. – A. K. Jul 04 '12 at 16:53
  • 3
    You could do the same with a function if you passed in `__LINE__`. – chris Jul 04 '12 at 16:53
  • @AdityaKumar: Except the third option won't give a useful line number. That's one good reason for (occasionally) using macros. – Mike Seymour Jul 04 '12 at 16:54
  • You are all right about printing the line number in the third option. I edited this. – White Zebra Jul 04 '12 at 16:59
  • 1
    I'd say there's nothing wrong with macros in C. In C++ on the other hand they're like the plaque: best avoided! Alone the fact that they don't follow scoping should be an indication on how evil macros can be! – MFH Jul 04 '12 at 19:58
  • 1
    I usually do both: a function like `exitOnFailure` and a macro EXIT_ON_FAILURE` which only passes parameters plus `__FILE__`, `__LINE__` and `__func__`. – Kos Jul 05 '12 at 11:06
  • 1
    @MFH they are a plaque? Use [these](http://en.wikipedia.org/wiki/Commemorative_plaque)? I think you meant plague. – Richard J. Ross III Jul 05 '12 at 14:04
  • I think I'd prefer a macro that calls the inline function, adding `__LINE__` and friends to the arguments. – cha0site Jul 05 '12 at 14:16
  • 5
    It seems like you are reinventing the assert() macro. – Vaughn Cato Jul 05 '12 at 14:18
  • Some functions return `-1` to report an error – anatolyg Jul 05 '12 at 14:28
  • @MFH can you elaborate on the scope issue? – White Zebra Jul 05 '12 at 14:51
  • 1
    @WhiteZebra in C++ every symbol resides in a namespace (may it be the global one). Besides namespaces, structs/classes, enums, unions and so on create a scope. The problem with macros is that they don't care about scopes! Take for example the function std::max, if you include Windows.h there will be a macro called max defined somewhere which leads to a compile error as max(a, b) not necessarily matches the parameter list of max, or will break completely as "std::" will be left in the code whereas the max-part as expanded. As macros simply replace text they're among the evil aspects of C/C++. – MFH Jul 05 '12 at 15:10

3 Answers3

3

Macros are a very powerful feature of C/C++ and like all C features are pointed at your foot by default. Consider the following use of your macro:

if (doSomething())
    EXIT_ON_FAILURE(s)   /* <-- MISSING SEMICOLON! OH NOES!!! */
else
    doSomethingElse();

Does the else belong to the if in the statement or the if created by expanding EXIT_ON_FAILURE? Either way, the behaviour of one missing semicolon is entirely unexpected. If EXIT_ON_FAILURE() were a function, you'd get a compiler error. In this case, you'd get code that compiles but does the wrong thing.

And that's the problem with macros. They look like functions or variables but they aren't. A badly-written macro is the gift that keeps on giving. Every use of the macro is a potential subtle bug and every change to a macro threatens to introduce logic errors into code that you didn't touch.

In general, you should avoid macros unless absolutely necessary.

If you need to define a constant, use a const variable or an enum. A good compiler (which you can get for free) will turn them into literals in the resulting executable just like a #define'd constant but it will also handle type conversions the way you expect and will show up in the debugger's symbol table.

If you need something like an inline function, use an inline function. C++ and C99 both provide them and most decent compilers (including the free ones) have done it as an extension for a long time.

Functions force their arguments to be evaluated, unlike macros, so

inline int DOUBLE(int a) {return a+a;}

will only ever evaluate a once while

#define DOUBLE(a) (a + a)

will evaluate a twice. This means that

x = DOUBLE(someVeryLongFunction());

will take twice as long if DOUBLE is a macro than if it is a function.

Also, I (deliberately) forgot to parenthesize the macro arguments, so this:

DOUBLE(a << b)

will give an entirely surprising result. You'd need to remember to write the DOUBLE macro as

#define DOUBLE(a) ((a) + (a))

In other words, you need to write a macro perfectly just to minimize the chances of shooting yourself in the foot. If you make a mistake, you'll be paying for it for years.

All that being said, yes, there are cases where a macro will make the code more readable. They are few and far between, but they exist. One of them is the assert macro which your code reinvents. It's pretty common for complex systems to use their own custom assert-like macro to tie into the local debugging scheme, and those are almost always implemented with macros in order to get at __FILE__, __LINE__ and the text of the condition.

But even then, this is how the typical assert is implemented:

#ifdef NDEBUG
#   define assert(cond)
#else
#   define assert(cond) __assert(cond, __FILE__, __LINE__, #cond)
#endif

In other words, the function-like macro expands into a function call. This way, when you call assert, the expansion is still pretty close to what it looks like and the argument expansion happens the way a you'd expect it to.

And there are a few other uses. Basically, anytime you need to pass information to the program from the build process itself, it will probably need to go through the macro system. Even then though, you should minimize the amount of code that touches the macro and how much the macro does.

One final thing. If you are tempted to use a macro because you think the code will be faster, be aware that this is the Devil talking. In the olden days, there may have been cases where converting small functions into macros gave a noticeable performance improvement. These days though:

  1. Most compilers support inline functions. Some even do it automatically to static functions.

  2. Modern computers are so fast that you almost certainly won't notice the overhead of calling even a trivial function.

Only if your compiler doesn't do inline functions and you can't just replace it with a better one and you've proven that function call overhead is a bottleneck can you maybe justify writing a few macros.

Maybe.

Chris Reuter
  • 1,458
  • 10
  • 8
  • +1 for "If you are tempted to use a macro because you think the code will be faster, be aware that this is the Devil talking." When you optimize prematurely, [you optimize with Hitler!](http://en.wikipedia.org/wiki/File:Ride_with_hitler.jpg) – Nicol Bolas Jul 05 '12 at 20:15
  • Great answer! especially the example of good macro usage (debug assert). – cade Feb 02 '18 at 05:35
0

Sure macros can simplify a function, making it easier to read. But you should consider using inline functions instead.

In your example, EXIT_ON_FAILURE, could be an inline function. Macros not just make compiler erros inaccurate (it may cause some erros show on the wrong place), there are some things to be careful when using macros, specially the variables, consider this example:

#define MY_MACRO(s) if (s * 2 >= 20) foo()

// later on your code:
MY_MACRO(5 + 5);

While one could expect foo() to me called, it won't, since it won't expand to if (10 * 2 >= 20) foo(), it will expand to if (5 + 5 * 2 >= 20) foo(). So you need to remember to always use () around your variables when defining your macros.

Macros also make the program harder to debug.

fbafelipe
  • 4,862
  • 2
  • 25
  • 40
0

There are certainly times when macros are what you need, but you should try to minimize the number of them. In your example, there is already a macro called "assert" which you could use instead of creating a new one.

C++ has many features that allow you to do things without macros that would require macros in C, so macros should be even less common in C++ code than in C code.

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • Sometimes you don't want to use assert since it might scare your users. An informative output might be better in those cases. – White Zebra Jul 05 '12 at 15:06
  • @WhiteZebra What should they be scared about? The idea behind assert is to use it in debug builds to find preventable problems before the software gets released. For that very reason the assert-macro gets defined to no action in release mode (see http://en.cppreference.com/w/cpp/error/assert)... – MFH Jul 05 '12 at 16:44