关于c预处理器:C ++宏什么时候有用?

关于c预处理器:C ++宏什么时候有用?

When are C++ macros beneficial?

C预处理器是由C++社区合理地害怕和回避的。在线性函数中,consts和template通常是比#define更安全、更好的替代方法。

以下宏:

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

绝不能优于类型安全:

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

但是宏确实有自己的位置,请列出在没有预处理器的情况下无法使用的宏的用途。

请将每个用例放在一个单独的答案中,以便投票表决,如果您知道如何在没有预评估者的情况下获得其中一个答案,请在该答案的评论中指出如何实现。


作为调试功能的包装器,自动传递如__FILE____LINE__等:

1
2
3
4
5
#ifdef ( DEBUG )
#define M_DebugLog( msg )  std::cout << __FILE__ <<":" << __LINE__ <<":" << msg
#else
#define M_DebugLog( msg )
#endif


方法必须始终是完整的、可编译的代码;宏可能是代码片段。因此,可以定义foreach宏:

1
#define foreach(list, index) for(index = 0; index < list.size(); index++)

并以此方式使用:

1
2
foreach(cookies, i)
    printf("Cookie: %s", cookies[i]);

由于C++ 11,这是由范围为基础的循环取代。


头文件保护需要宏。

还有其他需要宏的地方吗?不多(如果有的话)。

是否还有其他从宏中受益的情况?对!!!!

我使用宏的一个地方是非常重复的代码。例如,当包装C++代码与其他接口(.NET、COM、Python等)一起使用时,我需要捕获不同类型的异常。我是这样做的:

1
2
3
4
5
6
7
8
9
10
#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__); \
}

我必须在每个包装函数中放入这些捕获。我不是每次都键入完整的catch块,而是键入:

1
2
3
4
5
6
7
void Foo()
{
    try {
        ::mylib::Foo()
    }
    HANDLE_EXCEPTIONS
}

这也使得维护更加容易。如果必须添加一个新的异常类型,我只需要在一个地方添加它。

还有其他有用的例子:其中许多例子包括__FILE____LINE__预处理器宏。

总之,宏在正确使用时非常有用。宏不是邪恶的——它们的滥用是邪恶的。


主要是:

  • 包括警卫
  • 条件编译
  • 报告(如__LINE____FILE__等预定义宏)
  • (很少)复制重复的代码模式。
  • 在您的竞争对手代码中。

  • 在条件编译中,为了克服编译器之间的差异问题:

    1
    2
    3
    4
    5
    6
    7
    8
    #ifdef ARE_WE_ON_WIN32
    #define close(parm1)            _close (parm1)
    #define rmdir(parm1)            _rmdir (parm1)
    #define mkdir(parm1, parm2)     _mkdir (parm1)
    #define access(parm1, parm2)    _access(parm1, parm2)
    #define create(parm1, parm2)    _creat (parm1, parm2)
    #define unlink(parm1)           _unlink(parm1)
    #endif


    当您想从表达式中生成字符串时,最好的例子是assert(#xx的值转换为字符串)。

    1
    2
    3
    #define ASSERT_THROW(condition) \
    if (!(condition)) \
         throw std::exception(#condition" is false");


    字符串常量有时被更好地定义为宏,因为使用字符串文本比使用const char *可以做得更多。

    例如,字符串文本可以很容易地连接起来。

    1
    2
    3
    4
    #define BASE_HKEY"Software\\Microsoft\\Internet Explorer\"
    // Now we can concat with other literals
    RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY"Settings", &settings);
    RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY"TypedURLs", &URLs);

    如果使用了const char *,那么必须使用某种类型的字符串类在运行时执行连接:

    1
    2
    3
    const char* BaseHkey ="Software\\Microsoft\\Internet Explorer\";
    RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) +"
    Settings").c_str(), &settings);
    RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) +"
    TypedURLs").c_str(), &URLs);


    当您想更改程序流(return时,函数中的breakcontinue代码的行为与函数中实际内联的代码不同。

    1
    2
    3
    4
    5
    6
    #define ASSERT_RETURN(condition, ret_val) \
    if (!(condition)) { \
        assert(false && #condition); \
        return ret_val; }


    // should really be in a do { } while(false) but that's another discussion.

    明显的包括警卫

    1
    2
    3
    4
    5
    6
    #ifndef MYHEADER_H
    #define MYHEADER_H

    ...

    #endif


    不能使用常规函数调用执行函数调用参数的短路。例如:

    1
    2
    3
    4
    5
    6
    #define andm(a, b) (a) && (b)

    bool andf(bool a, bool b) { return a && b; }

    andm(x, y) // short circuits the operator so if x is false, y would not be evaluated
    andf(x, y) // y will always be evaluated


    C++的单元测试框架,类似于UnTest+++,非常适合于预处理器宏。几行单元测试代码扩展到一个类的层次结构中,手工输入一点也不有趣。没有像UnTest++这样的预处理器魔术,我不知道如何高效地编写C++的单元测试。


    比如说,我们会忽略一些明显的事情,比如头球后卫。

    有时,您希望生成需要由预编译器复制/粘贴的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #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)

    这使您能够对其进行编码:

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

    可以生成如下信息:

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

    请注意,将模板与宏混合可以得到更好的结果(即,自动将值与变量名并排生成)

    例如,其他时候,您需要一些代码的文件和/或行来生成调试信息。以下是Visual C++的经典:

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

    与以下代码相同:

    1
    #pragma message(WRNG"Hello World")

    它生成的消息如下:

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

    其他时候,您需要使用和串联运算符生成代码,例如为属性生成getter和setter(对于非常有限的情况,通过)。

    其他时候,如果通过函数使用,您将生成无法编译的代码,例如:

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

    可以用作

    1
    2
    3
    4
    5
    6
    MY_TRY
       doSomethingDangerous() ;
    MY_CATCH
       tryToRecoverEvenWithoutMeaningfullInfo() ;
       damnThoseMacros() ;
    MY_END_TRY

    (不过,我只看到这种代码正确使用过一次)

    最后,但并非最不重要的是,著名的boost::foreach!!!!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #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;
    }

    (注:代码从Boost主页复制/粘贴)

    这比std::for_each好得多。

    因此,宏总是有用的,因为它们超出了正常的编译器规则。但我发现大部分时间我都看到了,它们实际上是C代码的残余,从来没有翻译成合适的C++。


    害怕C预处理器就像害怕白炽灯一样,因为我们有荧光灯。是的,前者可能是电编程时间效率低下。是的,你会被他们烧伤。但如果你处理得当,他们可以完成这项工作。

    在编写嵌入式系统的程序时,C是除汇编程序之外唯一的选项。在桌面上用C++编程,然后切换到更小的嵌入式目标后,你就学会了不去担心如此多的裸C特征(包括宏)的"不雅",而只是想从这些特性中找出最好和安全的用法。

    亚历山大斯蒂芬诺夫说:

    When we program in C++ we should not be ashamed of its C heritage, but make
    full use of it. The only problems with C++, and even the only problems with C, arise
    when they themselves are not consistent with their own logic.


    一些非常先进和有用的东西仍然可以使用预处理器(宏)来构建,而使用C++的"语言构造"(包括模板)是不可能做到的。

    示例:

    使某个东西同时成为C标识符和字符串

    在C中使用枚举类型的变量作为字符串的简单方法

    Boost预处理器元编程


    代码重复。

    看看增强预处理器库,这是一种元编程。在主题->动机中,你可以找到一个很好的例子。


    我们使用__FILE____LINE__宏进行诊断,用于信息丰富的异常抛出、捕获和记录,以及我们的QA基础设施中的自动日志文件扫描器。

    例如,一个抛出的宏OUR_OWN_THROW可以与该异常的异常类型和构造函数参数一起使用,包括文本描述。这样地:

    1
    OUR_OWN_THROW(InvalidOperationException, (L"Uninitialized foo!"));

    这个宏当然会抛出InvalidOperationException异常,其中description作为构造器参数,但它也会将一条消息写到一个日志文件中,该日志文件包含抛出发生的文件名和行号及其文本描述。引发的异常将得到一个ID,该ID也将被记录。如果在代码中的其他地方捕获到异常,则会将其标记为异常,然后日志文件将指示已处理特定的异常,因此不太可能是导致稍后登录的任何崩溃的原因。未处理的异常可以很容易地被我们的自动化QA基础架构捕获。


    我偶尔使用宏,这样我可以在一个地方定义信息,但在代码的不同部分以不同的方式使用它。只是有点邪恶:)

    例如,在"field_list.h"中:

    1
    2
    3
    4
    5
    6
    7
    8
    /*
     * List of fields, names and values.
     */

    FIELD(EXAMPLE1,"first example", 10)
    FIELD(EXAMPLE2,"second example", 96)
    FIELD(ANOTHER,"more stuff", 32)
    ...
    #undef FIELD

    然后,对于公共枚举,可以定义为仅使用名称:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #define FIELD(name, desc, value) FIELD_ ## name,

    typedef field_ {

    #include"field_list.h"

        FIELD_MAX

    } field_en;

    在private init函数中,所有字段都可用于用数据填充表:

    1
    2
    3
    4
    5
    #define FIELD(name, desc, value) \
        table[FIELD_ ## name].desc = desc; \
        table[FIELD_ ## name].value = value;


    #include"field_list.h"


    一个常见的用途是检测编译环境,对于跨平台开发,您可以为Linux编写一组代码,也可以在没有跨平台库的情况下为Windows编写另一组代码。

    因此,在一个粗略的例子中,跨平台互斥体可以

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void lock()
    {
        #ifdef WIN32
        EnterCriticalSection(...)
        #endif
        #ifdef POSIX
        pthread_mutex_lock(...)
        #endif
    }

    对于函数,当您想要显式地忽略类型安全性时,它们是有用的。比如上面和下面做断言的很多例子。当然,就像很多C/C++特征一样,你可以用脚射击自己,但是语言给了你工具,让你决定做什么。


    有点像

    1
    2
    void debugAssert(bool val, const char* file, int lineNumber);
    #define assert(x) debugAssert(x,__FILE__,__LINE__);

    这样你就可以

    1
    assert(n == true);

    如果n为假,则将问题的源文件名和行号打印到日志中。

    如果使用普通函数调用,例如

    1
    void assert(bool val);

    与宏不同,您所能得到的只是将断言函数的行号打印到日志中,这样就不那么有用了。


    1
    #define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

    与当前线程中讨论的"首选"模板解决方案不同,您可以将其用作常量表达式:

    1
    2
    char src[23];
    int dest[ARRAY_SIZE(src)];


    我使用宏轻松定义异常:

    1
    DEF_EXCEPTION(RessourceNotFound,"Ressource not found")

    其中def_例外

    1
    2
    3
    4
    5
    6
    7
    8
    #define DEF_EXCEPTION(A, B) class A : public exception\
      {\
      public:\
        virtual const char* what() const throw()\
        {\
          return B;\
        };\
      }\


    您可以使用定义来帮助调试和单元测试场景。例如,创建内存函数的特殊日志变量,并创建一个特殊的memlog_preinclude.h:

    1
    2
    3
    #define malloc memlog_malloc
    #define calloc memlog calloc
    #define free memlog_free

    编译代码时使用:

    1
    gcc -Imemlog_preinclude.h ...

    memlog.o中指向最终图像的链接。现在您可以控制malloc等,可能是为了日志记录,或者为单元测试模拟分配失败。


    当您在编译时决定编译器/OS/硬件特定的行为时。

    它允许您建立与comppiler/os/硬件特定功能的接口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #if defined(MY_OS1) && defined(MY_HARDWARE1)
    #define   MY_ACTION(a,b,c)      doSothing_OS1HW1(a,b,c);}
    #elif define(MY_OS1) && defined(MY_HARDWARE2)
    #define   MY_ACTION(a,b,c)      doSomthing_OS1HW2(a,b,c);}
    #elif define(MY_SUPER_OS)
              /* On this hardware it is a null operation */
    #define   MY_ACTION(a,b,c)
    #else
    #error "PLEASE DEFINE MY_ACTION() for this Compiler/OS/HArdware configuration"
    #endif

    似乎到目前为止,人们只是间接地提到了虚拟货币:

    当编写泛型C++ 03代码时,需要一个可变数量的(通用)参数,可以使用宏而不是模板。

    1
    2
    3
    4
    5
    6
    7
    #define CALL_RETURN_WRAPPER(FnType, FName, ...)          \
      if( FnType theFunction = get_op_from_name(FName) ) {   \
        return theFunction(__VA_ARGS__);                     \
      } else {                                               \
        throw invalid_function_name(FName);                  \
      }                                                      \
    /**/

    注:一般来说,名称检查/抛出也可以合并到假设的get_op_from_name函数中。这只是一个例子。在va参数调用周围可能还有其他的通用代码。

    一旦我们得到了带有C++ 11的可变模板,我们就可以用模板来"适当"地解决这个问题。


    也许宏最常用于独立于平台的开发。考虑类型不一致的情况-使用宏,您可以简单地使用不同的头文件-例如:--赢的类型.h

    1
    typedef ...some struct

    --posix_类型.h

    1
    typedef ...some another struct

    --程序.h

    1
    2
    3
    4
    5
    6
    7
    #ifdef WIN32
    #define TYPES_H"WINTYPES.H"
    #else
    #define TYPES_H"POSIX_TYPES.H"
    #endif

    #include TYPES_H

    在我看来,它比用其他方式实现它更具可读性。


    还有一个foreach宏。T:类型,C:容器,I:迭代器

    1
    2
    #define foreach(T, c, i) for(T::iterator i=(c).begin(); i!=(c).end(); ++i)
    #define foreach_const(T, c, i) for(T::const_iterator i=(c).begin(); i!=(c).end(); ++i)

    用法(概念展示,非真实):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void MultiplyEveryElementInList(std::list<int>& ints, int mul)
    {
        foreach(std::list<int>, ints, i)
            (*i) *= mul;
    }

    int GetSumOfList(const std::list<int>& ints)
    {
        int ret = 0;
        foreach_const(std::list<int>, ints, i)
            ret += *i;
        return ret;
    }

    更好的实现:谷歌"Boost-ForEach"

    好文章:ConditionalLove:ForEach Redux(Eric Niebler)http://www.artima.com/cppsource/ForEach.html


    编译器可以拒绝您的内联请求。

    宏总是有自己的位置。

    我发现一些有用的东西是为调试跟踪定义调试——您可以在调试问题时(甚至在整个开发周期中)将其保留为1,然后在发布时将其关闭。


    在上一份工作中,我在做病毒扫描。为了让我调试更容易,我有很多日志记录卡在各处,但在这样一个高需求的应用程序中,函数调用的费用太高了。所以,我想到了这个小宏,它仍然允许我在客户站点上启用发布版本的调试日志记录,而不需要花费函数调用的费用,它会检查调试标志,然后返回而不记录任何内容,或者如果启用,它会做日志记录…宏定义如下:

    1
    #define dbgmsg(_FORMAT, ...)  if((debugmsg_flag  & 0x00000001) || (debugmsg_flag & 0x80000000))     { log_dbgmsg(_FORMAT, __VA_ARGS__);  }

    由于日志函数中有va_参数,对于这样的宏来说,这是一个很好的例子。

    在此之前,我在一个高安全性的应用程序中使用了一个宏,它需要告诉用户他们没有正确的访问权限,并且它会告诉他们需要什么标志。

    宏定义为:

    1
    2
    #define SECURITY_CHECK(lRequiredSecRoles) if(!DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, true)) return
    #define SECURITY_CHECK_QUIET(lRequiredSecRoles) (DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, false))

    然后,我们可以将检查撒在整个UI上,它将告诉您哪些角色可以执行您尝试执行的操作,如果您还没有该角色的话。其中两个的原因是在某些地方返回一个值,而在其他地方从一个空函数返回…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    SECURITY_CHECK(ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR);

    LRESULT CAddPerson1::OnWizardNext()
    {
       if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_EMPLOYEE) {
          SECURITY_CHECK(ROLE_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD ) -1;
       } else if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_CONTINGENT) {
          SECURITY_CHECK(ROLE_CONTINGENT_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR) -1;
       }
    ...

    不管怎样,这就是我使用它们的方式,我不确定这对模板有什么帮助…除此之外,我尽量避开它们,除非真的有必要。


    如果您有一个字段列表,可以用于许多事情,例如定义结构、将该结构序列化为某种二进制格式或从某种二进制格式序列化、执行数据库插入等,那么您可以(递归地!)使用预处理器避免重复域列表。

    这是公认的可怕。但有时可能比在多个地方更新一长串字段要好?我只用过一次这种技巧,这一次非常有用。

    当然,相同的一般思想在语言中被广泛地使用,并有适当的反映——只需对类进行instrospect并依次对每个字段进行操作。在C预处理器中执行该操作是脆弱的、难以辨认的,并且不总是可移植的。所以我有些不安地提到它。尽管如此,这是…

    (编辑:我现在看到这与@andrew johnson在9/18中所说的类似;但是递归地包含相同文件的想法需要更进一步。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    // file foo.h, defines class Foo and various members on it without ever repeating the
    // list of fields.

    #if defined( FIELD_LIST )
       // here's the actual list of fields in the class.  If FIELD_LIST is defined, we're at
       // the 3rd level of inclusion and somebody wants to actually use the field list.  In order
       // to do so, they will have defined the macros STRING and INT before including us.
       STRING( fooString )
       INT( barInt )  
    #else // defined( FIELD_LIST )

    #if !defined(FOO_H)
    #define FOO_H

    #define DEFINE_STRUCT
    // recursively include this same file to define class Foo
    #include"foo.h"
    #undef DEFINE_STRUCT

    #define DEFINE_CLEAR
    // recursively include this same file to define method Foo::clear
    #include"foo.h"
    #undef DEFINE_CLEAR

    // etc ... many more interesting examples like serialization

    #else // defined(FOO_H)
    // from here on, we know that FOO_H was defined, in other words we're at the second level of
    // recursive inclusion, and the file is being used to make some particular
    // use of the field list, for example defining the class or a single method of it

    #if defined( DEFINE_STRUCT )
    #define STRING(a)  std::string a;
    #define INT(a)     long a;
       class Foo
       {
          public:
    #define FIELD_LIST
    // recursively include the same file (for the third time!) to get fields
    // This is going to translate into:
    //    std::string fooString;
    //    int barInt;
    #include"foo.h"
    #endif

          void clear();
       };
    #undef STRING
    #undef INT
    #endif // defined(DEFINE_STRUCT)


    #if defined( DEFINE_ZERO )
    #define STRING(a) a ="";
    #define INT(a) a = 0;
    #define FIELD_LIST
       void Foo::clear()
       {
    // recursively include the same file (for the third time!) to get fields.
    // This is going to translate into:
    //    fooString="";
    //    barInt=0;
    #include"foo.h"
    #undef STRING
    #undef int
       }
    #endif // defined( DEFINE_ZERO )

    // etc...


    #endif // end else clause for defined( FOO_H )

    #endif // end else clause for defined( FIELD_LIST )


    使用-D/D选项,可以在编译器命令行上使用#define常量。在为多个平台交叉编译同一个软件时,这通常很有用,因为您可以让makefiles控制为每个平台定义的常量。


    我认为这个技巧是对不能用函数模拟的预处理器的巧妙使用:

    1
    2
    3
    4
    5
    6
    7
    8
    #define COMMENT COMMENT_SLASH(/)
    #define COMMENT_SLASH(s) /##s

    #if defined _DEBUG
    #define DEBUG_ONLY
    #else
    #define DEBUG_ONLY COMMENT
    #endif

    然后你可以这样使用它:

    1
    2
    cout <<"Hello, World!" <<endl;
    DEBUG_ONLY cout <<"This is outputed only in debug mode" <<endl;

    您还可以定义一个仅限发布的宏。


    我使用预处理器从嵌入式系统中不能在编译代码中使用浮点的浮点值计算定点数字。把你所有的数学都放在现实世界的单位中是很方便的,而不必用固定点来考虑它们。

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // TICKS_PER_UNIT is defined in floating point to allow the conversions to compute during compile-time.
    #define TICKS_PER_UNIT  1024.0


    // NOTE: The TICKS_PER_x_MS will produce constants in the preprocessor.  The (long) cast will
    //       guarantee there are no floating point values in the embedded code and will produce a warning
    //       if the constant is larger than the data type being stored to.
    //       Adding 0.5 sec to the calculation forces rounding instead of truncation.
    #define TICKS_PER_1_MS( ms ) (long)( ( ( ms * TICKS_PER_UNIT ) / 1000 ) + 0.5 )

    您可以在调试版本中启用额外的日志记录,并在没有布尔检查开销的情况下为发布版本禁用它。所以,不是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void Log::trace(const char *pszMsg) {
        if (!bDebugBuild) {
            return;
        }
        // Do the logging
    }

    ...

    log.trace("Inside MyFunction");

    您可以有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #ifdef _DEBUG
    #define LOG_TRACE log.trace
    #else
    #define LOG_TRACE void
    #endif

    ...

    LOG_TRACE("Inside MyFunction");

    如果没有定义调试,则不会生成任何代码。您的程序将运行得更快,跟踪日志记录的文本将不会编译到可执行文件中。


    在Visual Studio中,您需要一个用于资源标识符的宏,因为资源编译器只理解这些宏(即,它不适用于const或enum)。


    你能把它作为一个内联函数来实现吗?

    1
    #define my_free(x) do { free(x); x = NULL; } while (0)


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #define COLUMNS(A,B) [(B) - (A) + 1]

    struct
    {
        char firstName COLUMNS(  1,  30);
        char lastName  COLUMNS( 31,  60);
        char address1  COLUMNS( 61,  90);
        char address2  COLUMNS( 91, 120);
        char city      COLUMNS(121, 150);
    };


    宏可用于模拟switch语句的语法:

    1
    2
    3
    4
    5
    6
    switch(x) {
    case val1: do_stuff(); break;
    case val2: do_other_stuff();
    case val3: yet_more_stuff();
    default:   something_else();
    }

    对于非整数值类型。在这个问题中:

    在Switter语句中使用字符串——我们在C++ 17中的立场是什么?

    你会发现一些关于lambda的方法的答案,但不幸的是,正是宏让我们最接近:

    1
    2
    3
    4
    5
    6
    SWITCH(x)
    CASE val1  do_stuff(); break;
    CASE val2  do_other_stuff();
    CASE val3  yet_more_stuff();
    DEFAULT    something_else();
    END


    通常情况下,我会得到如下代码:

    1
    2
    3
    int SomeAPICallbackMethod(long a, long b, SomeCrazyClass c, long d, string e, string f, long double yx) { ... }
    int AnotherCallback(long a, long b, SomeCrazyClass c, long d, string e, string f, long double yx) { ... }
    int YetAnotherCallback(long a, long b, SomeCrazyClass c, long d, string e, string f, long double yx) { ... }

    在某些情况下,我会使用以下方法来简化我的生活:

    1
    2
    #define APIARGS long a, long b, SomeCrazyClass c, long d, string e, string f, long double yx
    int SomeAPICallbackMethod(APIARGS) { ... }

    它附带了真正隐藏变量名的警告,这在较大的系统中可能是一个问题,所以这并不总是正确的事情,只是有时。


    推荐阅读