In this article I want to do two things: to tell why macros are evil and how to deal with it, as well as to demonstrate a couple of C ++ macros that I use that simplify working with code and improve its readability. The tricks are not really dirty:
- Safe method call
- Unused variables
- String conversion
- Comma in the macro argument
- Endless cycle
I warn you in advance: if you are thinking of seeing something cool, puzzling and mind blowing under the cut, then there is nothing like that in the article. An article about the bright side of macros.
Some useful links
For beginners:
Anders Lindgren article
- Tips and tricks using the preprocessor (part one) covers the very basics of macros.
For advanced:
Anders Lindgren article
- Tips and tricks using the preprocessor (part two) covers more serious topics. Something will be in this article, but not all, and with fewer explanations.
For professionals: article (in English)
Aditya Kumar, Andrew Sutton, Bjarne Stroustrup - Rejuvenating C ++ Programs through Demacrofication , describes the possibilities for replacing macros with C ++ 11 features.
Slight cultural difference
According to
Wikipedia and my own feelings, in Russian we usually mean by the word “macro” this:
#define FUNC(x, y) ((x)^(y))
And the following:
#define VALUE 1
we call it the “preprocessor constant” (or simply “defaults”). In English, a little bit wrong: the first is called function-like macro, and the second is object-like macro (again, I’ll provide a link to
Wikipedia ). That is, when they talk about macros, they can mean both one and the other, and all together. Be careful when reading English texts.
')
What is good and what is bad
Recently, the popular opinion is that
macros are evil . Opinion is not groundless, but, in my opinion, needs clarification.
In one of the answers to the question
Why are the preprocessor macros evil and what are the alternatives? I found a fairly complete list of reasons forcing us to consider macros as evil and some ways to get rid of them. Below I will give the same list in Russian, but the examples and solutions to the problems will not be exactly the same as in the indicated link.
Macros cannot be debuggedFirst, in fact, you can:
Go to either "Properties". Under Configuration Properties-> C / C ++ -> Preprocessor, set "Generate Prepared File", you prefer. This will show what your macro expands to in context. If you need to compile a code, you need to go.
So, it would be more correct to say that “macros are difficult to debug”. But, nevertheless, the problem with debugging macros exists.
To determine if the macro you are using needs debugging, consider whether there is something in it that you want to push a breakpoint for. This can be a change in values ​​obtained through parameters, a declaration of variables, a change in objects or data from the outside, and the like.
Solutions to the problem:
- get rid of macros completely by replacing them with functions (you can inline if this is important),
- the logic of macros is transferred to functions, and the macros themselves must be made responsible only for the transfer of data to these functions,
- use only macros that do not require debugging.
When deploying a macro, strange side effects may occur.In order to show which side effects are involved, an example is usually given with arithmetic operations. I, too, will not depart from this tradition:
#include <iostream> #define SUM(a, b) a + b int main() { // x? int x = SUM(2, 2); std::cout << x << std::endl; x = 3 * SUM(2, 2); std::cout << x << std::endl; return 0; }
In the output we expect 4 and 12, and we get 4 and 8. The fact is that the macro simply substitutes the code where indicated. And in this case, the code will look like this:
int x = 3 * 2 + 2;
This is a side effect. For everything to work, as expected, you need to change our macro:
#include <iostream> #define SUM(a, b) (a + b) int main() { // x? int x = SUM(2, 2); std::cout << x << std::endl; x = 3 * SUM(2, 2); std::cout << x << std::endl; return 0; }
Now is true. But that is not all. Let's go to the multiplication:
#define MULT(a, b) a * b
Immediately, we write it “correctly”, but use it a little differently:
#include <iostream> #define MULT(a, b) (a * b) int main() { // x? int x = MULT(2, 2); std::cout << x << std::endl; x = MULT(3, 2 + 2); std::cout << x << std::endl; return 0; }
Deja vu: again we get 4 and 8. In this case, the expanded macro will look like:
int x = (3 * 2 + 2);
That is, now we need to write:
#define MULT(a, b) ((a) * (b))
Use this version of the macro and voila:
#include <iostream> #define MULT(a, b) ((a) * (b)) int main() { // x? int x = MULT(2, 2); std::cout << x << std::endl; x = MULT(3, 2 + 2); std::cout << x << std::endl; return 0; }
Now everything is correct.
If we abstract from arithmetic operations, then, in general, when writing macros, we need
- brackets around the whole expression
- brackets around each of the macro parameters
That is, instead
#define CHOOSE(ifC, chooseA, otherwiseB) ifC ? chooseA : otherwiseB
must be
#define CHOOSE(ifC, chooseA, otherwiseB) ((ifC) ? (chooseA) : (otherwiseB))
This problem is aggravated by the fact that not all types of parameters can be wrapped in brackets (the real example will be further in the article). Because of this, it is quite difficult to make high-quality macros.
In addition, as the
encyclopedist recalled in the comments, there are times when brackets do not save:
In the paragraph about side effects, you still forgot to mention a frequent problem - macros can calculate their arguments several times. In the worst case, this leads to strange side effects, and more easily, to performance problems.
Example
#define SQR(x) ((x) * (x)) y = SQR(x++);
Solutions to the problem:
- drop macros in favor of functions
- use macros with a clear name, a simple implementation and well-placed brackets, so that a programmer using such a macro can easily understand how to use it correctly.
Macros have no namespaceIf any macro is declared, it is not only global, but also simply will not allow to use something with the same name (the implementation of the macro will always be substituted). The most probably well-known example is the
problem with min and max under Windows .
The solution to the problem is to choose names for macros that are less likely to intersect with something, for example:
- names in UPPERCASE, they can usually intersect only with other macro names,
- names with a prefix (the name of your project, namespace, something else unique), intersection with other names will be possible with very little probability, but it will be a little more difficult for people to use such macros outside of your project.
Macros can do something that you do not suspect.In fact, this is the problem of choosing a name for a macro. Let's say we take the same example, which is given in the answer by reference:
#define begin() x = 0 #define end() x = 17 ... a few thousand lines of stuff here ... void dostuff() { int x = 7; begin(); ... more code using x ... printf("x=%d\n", x); end(); }
Here there are incorrectly chosen names that are misleading. If the macros were called set0toX () and set17toX () or something similar, problems would have been avoided.
Solutions to the problem:
- correctly call macros,
- replace macros with functions
- Do not use macros that implicitly change anything.
After all of the above, you can define "good" macros. Good macros are macros that
- do not require debugging (there’s simply no need to put a breakpoint inside)
- do not have side effects when unfolding (everything is wrapped in brackets)
- do not conflict with names anywhere (such kind of names are chosen, which with a small degree of probability will be used by someone else)
- do not change anything implicitly (the name accurately reflects what the macro does, and all work with the surrounding code, if possible, is carried out only through parameters and “return value”)
Safe method call
The old version, not tested Habrom #define prefix_safeCall(value, object, method) ((object) ? ((object)->method) : (value)) #define prefix_safeCallVoid(object, method) ((object) ? ((void)((object)->method)) : ((void)(0)))
Actually, I used this version #define prefix_safeCall(defaultValue, objectPointer, methodWithArguments) ((objectPointer) ? ((objectPointer)->methodWithArguments) : (defaultValue)) #define prefix_safeCallVoid(objectPointer, methodWithArguments) ((objectPointer) ? static_cast<void>((objectPointer)->methodWithArguments) : static_cast<void>(0))
But Habr is not an IDE, so these long lines look ugly (at least on my monitor), and I shortened them to a readable form.
tenzink in the comments
pointed out the problem with these macros , which I safely did not consider when writing the article:
prefix_safeCallVoid(getObject(), method());
With this call, getObject will be called twice.
Unfortunately, as the article showed, not every programmer will guess about this, so I can no longer consider these macros to be good. :-(
Nevertheless, I encountered similar macros (somewhat differently implemented) in real production code, they were used by a team of programmers, including me. Any problems because of them in my memory did not arise
The new version, which appeared thanks to
lemelisk and C ++ 14:
#define prefix_safeCall(defaultValue, objectPointer, methodWithArguments)\ [&](auto&& ptr) -> decltype(auto)\ {\ return ptr ? (ptr->methodWithArguments) : (defaultValue);\ }\ (objectPointer) #define prefix_safeCallVoid(objectPointer, methodWithArguments)\ [&](auto&& ptr)\ {\ if(ptr)\ (ptr->methodWithArguments); \ }\ (objectPointer)
Version for C ++ 11 #define prefix_safeCallBaseExpression(defaultValue, objectPointer, methodWithArguments)\ ((ptr) ? ((ptr)->methodWithArguments) : (defaultValue)) #define prefix_safeCall(defaultValue, objectPointer, methodWithArguments)\ [&](decltype((objectPointer))&& ptr)\ -> decltype(prefix_safeCallBaseExpression(defaultValue, ptr, methodWithArguments))\ {\ return prefix_safeCallBaseExpression(defaultValue, ptr, methodWithArguments);\ }\ (objectPointer) #define prefix_safeCallVoid(objectPointer, methodWithArguments)\ [&](decltype((objectPointer))&& ptr)\ {\ if (ptr)\ (ptr->methodWithArguments);\ }\ (objectPointer)
Notice the methodWithArguments parameter. This is the very example of a parameter that cannot be wrapped with parentheses. This means that in addition to calling the method in the parameter, you can stuff something else. However, by chance it is quite problematic to arrange this, so I do not consider these macros to be “bad”.
In addition, now we have added overhead to the call of lambda. Theoretically, it can be assumed that the lambda, called in the same place where it is defined, will be inline. But I did not find confirmation of this on the network, so it would be best to check it “manually” for your compiler.
How these two macros are used is, I think, understandable. If we have code:
auto somePointer = ...; if(somePointer) somePoiter->callSomeMethod();
then using the safeCallVoid macro, it turns into:
auto somePointer = ...; prefix_safeCallVoid(somePointer, callSomeMethod());
and, similarly, for the case with the return value:
auto somePointer = ...; auto x = prefix_safeCall(0, somePointer, callSomeMethod());
For what? First of all, these macros allow you to increase the readability of the code, reduce nesting. The greatest positive effect is given in conjunction with small methods (that is, if you follow the principles of refactoring).
Unused variables
#define prefix_unused(variable) ((void)variable)
Actually, the version I use is also different. #define prefix_unused1(variable1) static_cast<void>(variable1) #define prefix_unused2(variable1, variable2) static_cast<void>(variable1), static_cast<void>(variable2) #define prefix_unused3(variable1, variable2, variable3) static_cast<void>(variable1), static_cast<void>(variable2), static_cast<void>(variable3) #define prefix_unused4(variable1, variable2, variable3, variable4) static_cast<void>(variable1), static_cast<void>(variable2), static_cast<void>(variable3), static_cast<void>(variable4) #define prefix_unused5(variable1, variable2, variable3, variable4, variable5) static_cast<void>(variable1), static_cast<void>(variable2), static_cast<void>(variable3), static_cast<void>(variable4), static_cast<void>(variable5)
Please note that, starting from two parameters, this macro can theoretically have side effects. For greater reliability, you can use the classics:
#define unused2(variable1, variable2) do {static_cast<void>(variable1); static_cast<void>(variable2);} while(false)
But, in this form, it is harder to read, which is why I use the less “safe” option.
Such a macro is, for example, in cocos2d-x, where it is called CC_UNUSED_PARAM. Among the shortcomings: theoretically, it may not work on all compilers. However, in cocos2d-x it is defined exactly the same for all platforms.
Using:
int main() { int a = 0;
For what? This macro avoids a warning about an unused variable, and it seems to the reader who reads the code: “the one who wrote this knew that the variable is not used, everything is in order”.
String conversion
#define prefix_stringify(something) std::string(#something)
Yes, this is so severe, immediately in std :: string. We will leave the pros and cons of using the string class outside the scope of the conversation, let's talk only about the macro.
You can use it like this:
std::cout << prefix_stringify("string\n") << std::endl;
And so:
std::cout << prefix_stringify(std::cout << prefix_stringify("string\n") << std::endl;) << std::endl;
And even so:
std::cout << prefix_stringify(#define prefix_stringify(something) std::string(#something) std::cout << prefix_stringify("string\n") << std::endl;) << std::endl;
However, in the last example, the line break will be replaced with a space. For real transfer you need to use '\ n':
std::cout << prefix_stringify(#define prefix_stringify(something) std::string(#something)\nstd::cout << prefix_stringify("string\n") << std::endl;) << std::endl;
You can also use other characters, for example, '\' for string concatenation, '\ t', and others.
For what? It can be used to simplify the output of debug information or, for example, to create a factory of objects with text id (in this case, such a macro can be used when registering a class in a factory to turn a class name into a string).
Comma in macro parameter
#define prefix_singleArgument(...) __VA_ARGS__
The idea is peeped here .
An example from the same place:
#define FOO(type, name) type name FOO(prefix_singleArgument(std::map<int, int>), map_var);
For what? It is used when it is necessary to transfer an argument to another macro, containing commas, as one argument and the impossibility to use brackets for this.
Endless cycle
#define forever() for(;;)
Version by Joel Spolsky #define ever (;;) for ever { ... }
PS If someone, following the link, did not guess to read the name of the question, then it sounds like “what is the worst real abuse of macros you have encountered?” ;-)
Using:
int main() { bool keyPressed = false; forever() { ... if(keyPressed) break; } return 0; }
For what? When while (true), while (1), for (;;), and other standard loop creation paths do not seem too informative, you can use a similar macro. The only plus he gives is a slightly better readability of the code.
Conclusion
When used correctly, macros are not at all something bad. The main thing is not to abuse them and follow the simple rules for creating "good" macros. And then they will become your best assistants.
Upd . Returned to the article "Safe method call", thanks to
lemelisk for the hint with lambdas.
PS
And what interesting macros do you use in your projects? Feel free to share in the comments.