When are C++ macros beneficial? [closed]

2020-01-23 02:14发布

The C preprocessor is justifiably feared and shunned by the C++ community. In-lined functions, consts and templates are usually a safer and superior alternative to a #define.

The following macro:

#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)  

is in no way superior to the type safe:

inline bool succeeded(int hr) { return hr >= 0; }

But macros do have their place, please list the uses you find for macros that you can't do without the preprocessor.

Please put each use-cases in a seperate answer so it can be voted up and if you know of how to achieve one of the answers without the preprosessor point out how in that answer's comments.

30条回答
一纸荒年 Trace。
2楼-- · 2020-01-23 02:53

Mostly:

  1. Include guards
  2. Conditional compilation
  3. Reporting (predefined macros like __LINE__ and __FILE__)
  4. (rarely) Duplicating repetitive code patterns.
  5. In your competitor's code.
查看更多
萌系小妹纸
3楼-- · 2020-01-23 02:53

Seems VA_ARGS have only been mentioned indirectly so far:

When writing generic C++03 code, and you need a variable number of (generic) parameters, you can use a macro instead of a template.

#define CALL_RETURN_WRAPPER(FnType, FName, ...)          \
  if( FnType theFunction = get_op_from_name(FName) ) {   \
    return theFunction(__VA_ARGS__);                     \
  } else {                                               \
    throw invalid_function_name(FName);                  \
  }                                                      \
/**/

Note: In general, the name check/throw could also be incorporated into the hypothetical get_op_from_name function. This is just an example. There might be other generic code surrounding the VA_ARGS call.

Once we get variadic templates with C++11, we can solve this "properly" with a template.

查看更多
你好瞎i
4楼-- · 2020-01-23 02:54

As wrappers for debug functions, to automatically pass things like __FILE__, __LINE__, etc:

#ifdef ( DEBUG )
#define M_DebugLog( msg )  std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif
查看更多
孤傲高冷的网名
5楼-- · 2020-01-23 02:54

Header file guards necessitate macros.

Are there any other areas that necessitate macros? Not many (if any).

Are there any other situations that benefit from macros? YES!!!

One place I use macros is with very repetitive code. For example, when wrapping C++ code to be used with other interfaces (.NET, COM, Python, etc...), I need to catch different types of exceptions. Here's how I do that:

#define HANDLE_EXCEPTIONS \
catch (::mylib::exception& e) { \
    throw gcnew MyDotNetLib::Exception(e); \
} \
catch (::std::exception& e) { \
    throw gcnew MyDotNetLib::Exception(e, __LINE__, __FILE__); \
} \
catch (...) { \
    throw gcnew MyDotNetLib::UnknownException(__LINE__, __FILE__); \
}

I have to put these catches in every wrapper function. Rather than type out the full catch blocks each time, I just type:

void Foo()
{
    try {
        ::mylib::Foo()
    }
    HANDLE_EXCEPTIONS
}

This also makes maintenance easier. If I ever have to add a new exception type, there's only one place I need to add it.

There are other useful examples too: many of which include the __FILE__ and __LINE__ preprocessor macros.

Anyway, macros are very useful when used correctly. Macros are not evil -- their misuse is evil.

查看更多
来,给爷笑一个
6楼-- · 2020-01-23 02:55

Let's say we'll ignore obvious things like header guards.

Sometimes, you want to generate code that needs to be copy/pasted by the precompiler:

#define RAISE_ERROR_STL(p_strMessage)                                          \
do                                                                             \
{                                                                              \
   try                                                                         \
   {                                                                           \
      std::tstringstream strBuffer ;                                           \
      strBuffer << p_strMessage ;                                              \
      strMessage = strBuffer.str() ;                                           \
      raiseSomeAlert(__FILE__, __FUNCSIG__, __LINE__, strBuffer.str().c_str()) \
   }                                                                           \
   catch(...){}                                                                \
   {                                                                           \
   }                                                                           \
}                                                                              \
while(false)

which enables you to code this:

RAISE_ERROR_STL("Hello... The following values " << i << " and " << j << " are wrong") ;

And can generate messages like:

Error Raised:
====================================
File : MyFile.cpp, line 225
Function : MyFunction(int, double)
Message : "Hello... The following values 23 and 12 are wrong"

Note that mixing templates with macros can lead to even better results (i.e. automatically generating the values side-by-side with their variable names)

Other times, you need the __FILE__ and/or the __LINE__ of some code, to generate debug info, for example. The following is a classic for Visual C++:

#define WRNG_PRIVATE_STR2(z) #z
#define WRNG_PRIVATE_STR1(x) WRNG_PRIVATE_STR2(x)
#define WRNG __FILE__ "("WRNG_PRIVATE_STR1(__LINE__)") : ------------ : "

As with the following code:

#pragma message(WRNG "Hello World")

it generates messages like:

C:\my_project\my_cpp_file.cpp (225) : ------------ Hello World

Other times, you need to generate code using the # and ## concatenation operators, like generating getters and setters for a property (this is for quite a limited cases, through).

Other times, you will generate code than won't compile if used through a function, like:

#define MY_TRY      try{
#define MY_CATCH    } catch(...) {
#define MY_END_TRY  }

Which can be used as

MY_TRY
   doSomethingDangerous() ;
MY_CATCH
   tryToRecoverEvenWithoutMeaningfullInfo() ;
   damnThoseMacros() ;
MY_END_TRY

(still, I only saw this kind of code rightly used once)

Last, but not least, the famous boost::foreach !!!

#include <string>
#include <iostream>
#include <boost/foreach.hpp>

int main()
{
    std::string hello( "Hello, world!" );

    BOOST_FOREACH( char ch, hello )
    {
        std::cout << ch;
    }

    return 0;
}

(Note: code copy/pasted from the boost homepage)

Which is (IMHO) way better than std::for_each.

So, macros are always useful because they are outside the normal compiler rules. But I find that most the time I see one, they are effectively remains of C code never translated into proper C++.

查看更多
一纸荒年 Trace。
7楼-- · 2020-01-23 02:55

Some very advanced and useful stuff can still be built using preprocessor (macros), which you would never be able to do using the c++ "language constructs" including templates.

Examples:

Making something both a C identifier and a string

Easy way to use variables of enum types as string in C

Boost Preprocessor Metaprogramming

查看更多
登录 后发表回答