简体   繁体   中英

What would make C++ preprocessor macros an accepted development tool?

Apparently the preprocessor macros in C++ are

justifiably feared and shunned by the C++ community.

However, there are several cases where C++ macros are beneficial .

Seeing as preprocessor macros can be extremely useful and can reduce repetitive code in a very straightforward manner --

-- leaves me with the question, what exactly is it that makes preprocessor macros "evil", or, as the question title says, which feature ( or removal of feature ) would be needed from preprocessor macros to make them useful as a "good" development tool (instead of a fill-in that everyone's ashamed of when using it). (After all, the Lisp languages seem to embrace macros.)

Please Note : This is not about #include or #pragma or #ifdef . This is about #define MY_MACRO(...)...

Note: I do not intend for this question to be subjective. Should you think it is, feel free to vote to move it to programmers.SE.

Macros are widely considered evil because the preprocessor is a stupid text replacement tool that has little to none knowledge of C/C++.

Four very good reasons why macros are evil can be found in the C++ FAQ Lite .

Where possible, templates and inline functions are a better choice. The only reason I can think of why C++ still needs the preprocessor is for #include s and comment removal.

A widely disputed advantage is to use it to reduce code repetition; but as you can see by the boost preprocessor library, much effort has to be put to abuse the preprocessor for simple logic such as loops, leading to ugly syntax. In my opinion, it is a better idea to write scripts in a real high-level programming language for code generation instead of using the preprocessor.

Most preprocessor abuse come from misunderstanding, to quote Paul Mensonides(the author of the Boost.Preprocessor library):

Virtually all issues related to the misuse of the preprocessor stems from attempting to make object-like macros look like constant variables and function-like macro invocations look like underlying-language function calls. At best, the correlation between function-like macro invocations and function calls should be incidental. It should never be considered to be a goal. That is a fundamentally broken mentality.

As the preprocessor is well integrated into C++, its easier to blur the line, and most people don't see a difference. For example, ask someone to write a macro to add two numbers together, most people will write something like this:

#define ADD(x, y) ((x) + (y))

This is completely wrong. Runs this through the preprocessor:

#define ADD(x, y) ((x) + (y))
ADD(1, 2) // outputs ((1) + (2))

But the answer should be 3, since adding 1 to 2 is 3. Yet instead a macro is written to generate a C++ expression. Not only that, it could be thought of as a C++ function, but its not. This is where it leads to abuse. Its just generating a C++ expression, and a function is a much better way to go.

Furthermore, macros don't work like functions at all. The preprocessor works through a process of scanning and expanding macros, which is very different than using a call stack to call functions.

There are times it can be acceptable for macros to generate C++ code, as long as it isn't blurring the lines. Just like if you were to use python as a preprocessor to generate code, the preprocessor can do the same, and has the advantage that it doesn't need an extra build step.

Also, the preprocessor can be used with DSLs, like here and here , but these DSLs have a predefined grammar in the preprocessor, that it uses to generate C++ code. Its not really blurring the lines since it uses a different grammar.

Macros have one notable feature - they are very easy to abuse and rather hard to debug. You can write just about anything with macros, then macros are expanded into one-liners and when nothing works you have very hard time debugging the resulting code.

The feature alone makes one think ten times on whether and how to use macros for their task.

And don't forget that macros are expanded before actual compilation, so they automatically ignore namespaces, scopes, type safety and a ton of other things.

The most important thing about macros is that they have no scope, and do not care about context. They are almost a dump text replacement tool. So when you #define max(.... then everywhere where you have a max it gets replaced; so if someone adds overly generic macro names in their headers, they tend to influence code that they were not intended to.

Another thing is that when used without care, they lead to quite hard to read code, since no one can easily see what the macro could evaluate to, especially when multiple macros are nested.

A good guideline is to choose unique names, and when generating boilerplate code, #undef them as soon as possible to not pollute the namespace.

Additionally, they do not offer type safety or overloading.

Sometimes macros are arguably a good tool to generate boilerplate code, like with the help of boost.pp you could create a macro that helps you creating enums like:

ENUM(xenum,(a,b,(c,7)));

which could expand to

enum xenum { a, b, c=7 };

std::string to_string( xenum x ) { .... }

Things like assert() that need to react on NDEBUG are also often easier to implement as macros

There a many uses where a C developper uses Macros and an C++ developper uses templates.

There obviously corner cases where they're useful, but most of the time it's bad habits from the C world applied to C++ by people that believe there such a language called C/C++

So it's easier to say "it's evil" than risking a developper misuses them.

Forcing the programmer to use proper naming for the macros... and better tools to track replacement of macros would fix most my problems. I can't really say I've had major issues so far... It's something you burn yourself with and learn to take special care later on. But they badly need better integration with IDEs, debuggers.

  1. Macros do not offer type safety
  2. Problems where parameters are executed twice eg #define MAX(a,b) ((a)>(b)? (a): (b)) and apply it for MAX(i++, y--)
  3. Problems with debugging as their names do not occur in the symbol table.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM