开发者

Advantage of macro over in-line in C++

We know t开发者_开发百科hat in-line are favorable as they are checked by the compiler and same operation ( like ++x ) does not evaluate more than once when passed as an argument as compared to macros.

But in an interview I was asked the specific advantages or the circumstances when a macro is more favorable to inline in C++.

Does anyone know the answer or can give a thought on this question ?


The only thing I can think of is there are some tricks that you can do with a macro that can't be done with an inline function. Pasting tokens together at compile-time, and that sort of hackery.


Here is a specific situation where macros are not only preferred, they are actually the only way to accomplish something.

If you want to write a logging function which logs not only some message, but the file & line number of where the instance occured, you can either call your function directly, typing in the file & line values (or macros) directly:

LogError("Something Bad!", __FILE__, __LINE__);

...or, if you want it to work automatically, you must rely on a macro (warning: not compiled):

#define LogErrorEx(ERR) (LogError(ERR, __FILE__, __LINE__))
// ...
LogErrorEx("Something Else Bad!");

This cannot be achieved using templates, default parameters, default construction, or any other device in C++.


Sometimes you want to extend the language in ways that aren't possible with any other method.

#include <iostream>

#define CHECK(x) if (x); else std::cerr << "CHECK(" #x ") failed!" << std::endl

int main() {
    int x = 053;
    CHECK(x == 42);
    return 0;
}

This prints CHECK(x == 42) failed!.


In C++ specifically, one usage of MACROs that seem pop up very often (except for the debug print with file and line) is the use of MACROs to fill in a set of standard methods in a class that cannot be inherited from a base class. In some libraries that create custom mechanisms of RTTI, serialization, expression templates, etc., they often rely on a set of static const variables and static methods (and possibly special semantics for some overloaded operators that cannot be inherited) which are almost always the same but need to be added to any class that the user defines within this framework. In these cases, MACROs are often provided such that the user doesn't have to worry about putting all the necessary code (he only has to invoke the MACRO with the require info). For example, if I make a simple RTTI (Run-Time Type Identification) system, I might require that all classes have a TypeID and be dynamically castable:

class Foo : public Bar {
  MY_RTTI_REGISTER_CLASS(Foo, Bar, 0xBAADF00D)
};   

#define MY_RTTI_REGISTER_CLASS(CLASSNAME,BASECLASS,UNIQUEID) \
  public:\
    static const int TypeID = UNIQUEID;\
    virtual void* CastTo(int aTypeID) {\
      if(aTypeID == TypeID)\
        return this;\
      else\
        return BASECLASS::CastTo(aTypeID);\
    };

The above could not be done with templates or inheritance, and it makes the user's life easier and avoids code repetition.

I would say that this kind of use of MACROs is by far the most common in C++.


As already said, macros can use preprocessor directives: __FILE__, __LINE__ for instance, but of course #include and #define can also be useful to parameter behaviour:

#ifdef __DEBUG__
#    define LOG(s) std:cout << s << std:endl
#else
#    define LOG(s)
#endif

Depending wether __DEBUG__ is defined or not (via #define or via compiler options), the LOG macro will be active or not. This is an easy way to have debug info everywhere in your code that can be easily de-activated.

You can also think of changing the way memory is allocated (malloc will be redefined to target a memory pool instead of the standard heap for instance, etc...).


Inline functions are, as the name indicates, restricted to functional tasks, execution of some code.

Macros have a much broader application they may expand e.g to declarations or replace entire language constructs. Some examples (written for C and C++) that can't be done with functions:

typedef struct POD { double a; unsigned b } POD;
#declare POD_INITIALIZER { 1.0, 37u }

POD myPOD = POD_INITIALIZER;

#define DIFFICULT_CASE(X) case (X)+2 :; case (X)+3
#define EASY_CASE(X) case (X)+4 :; case (X)+5

switch (a) {
   default: ++a; break;
   EASY_CASE('0'): --a; break;
   DIFFICULT_CASE('A'): a = helperfunction(a); break;
}

#define PRINT_VALUE(X)                        \
do {                                          \
 char const* _form = #X " has value 0x%lx\n"; \
 fprintf(stderr, _form, (unsigned long)(X));  \
} while (false)

In the context of C++, Boost has a lot of more examples that are more involved and more useful.

But because with such macros you are in some sort extending the language (not strictly, the preprocessor is part of it) many people dislike macros, particularly in the C++ community, a bit less in the C community.

In any case, if you use such constructs you should always be very clear in what the should achieve, document well, and fight against the temptation to obfuscate your code.


A macro is just like a text replacement definition.

These essential differences that come into my mind are:

  • It must not be function-like. I mean it must not necessarily contain some consistent set of brackets for example.
  • It can be used elsewhere. Like in a class declaration scope or even in the global scope. So it must not be in the scope of another function.

You must use them if you want to perform actions that are impossible to be performed using functions:

  • initializing complicated tables (makes core more readable)
  • ease declaration of some special members like event IDs or tag classes (used a lot in MFC IMPLEMENT_DYNAMIC)
  • squeeze repetitive declarations at the beginning of functions
  • the already mentioned use of __LINE__, __FILE__, ... for logging


    #include <stdio.h>
    #define sq(x) x*x
    int main()
    {  
        printf("%d", sq(2+1));
        printf("%d", sq(2+5));
        return 0;
    }

The output for this code are 5 and 17. Macros expand textually. Its not like functions.

Explanation for this example:

sq(2+1) = 2+1*2+1 = 2+2+1 = 5

sq(2+5) = 2+5*2+5 = 2+10+5 = 17


I would add two uses:

  1. MIN and MAX, until C++0x, because the return type had to be declared by hand, mixed min and max as inlined functions would have been nightmarish, while a simple macro did it in the blink of an eye.
  2. privacy: you can always undef the macro before exiting your header, you cannot "undeclare" an inline function (or another symbol). This is due to the absence of proper modularity in C and C++ languages.
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜