質問

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?

役に立ちましたか?

解決

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.

他のヒント

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.

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.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top