C++ 宏有什么好的用途吗?

C++ 宏有什么好的用途吗?,c++,macros,C++,Macros,据我所知,宏在编译器正确看到程序文本之前会重新排列程序文本,因此可能会导致问题。我几乎从来没有见过他们在C++代码,主要是在C.< 我所知道的唯一好的用法是包含保护(#ifndef) 是否还有其他需要用宏来完成且不能以更简洁的方式实现的操作?可以从编译器命令行定义宏,使用-DFOO,定义宏FOO。这通常用于条件编译,例如已知在某些平台上工作但在其他平台上不工作的某个优化。构建系统可以检测优化是否可行,并使用这种宏启用它 这是我认为可以很好地使用宏的少数几个用途之一。但是,当然也有可能滥用此功能。

据我所知,宏在编译器正确看到程序文本之前会重新排列程序文本,因此可能会导致问题。我几乎从来没有见过他们在C++代码,主要是在C.< 我所知道的唯一好的用法是包含保护(
#ifndef


是否还有其他需要用宏来完成且不能以更简洁的方式实现的操作?

可以从编译器命令行定义宏,使用
-DFOO
,定义宏
FOO
。这通常用于条件编译,例如已知在某些平台上工作但在其他平台上不工作的某个优化。构建系统可以检测优化是否可行,并使用这种宏启用它


这是我认为可以很好地使用宏的少数几个用途之一。但是,当然也有可能滥用此功能。

有些用于优化的代码重写似乎不适用于模板元编程,并且需要宏。下面是一个可能的例子:

在C++11之前,您通常会将
static\u assert
定义为一个宏(如果条件为false,则类型定义将无效),因此它可以从任何位置(命名空间级别或函数级别)使用,并且仍然不会含糊不清(例如,通过使用行号)

这是使用宏减少高度冗余代码量的另一个很好的例子,也是另一个与可变模板不太相关的例子


此外,宏被广泛用于与编译器“对话”,例如,检查正在运行的编译器、编译器的版本、是否提供C++11支持等。

日志记录异常

宏允许您轻松地捕获
\uuuuu文件\uuuuuu
\uuuu行\uuuuu
\uuuu函数
。哦,当然你每次都可以手动编写它们,但坦率地说,这既单调又容易出错(无论是
\uuuuu FILE\uuuuuuuu
还是
\uuuuu func\uuuuuuu
都是C-string,所以你可能会把它们混在一起)。

是的,X-macro技巧总是很有用的。 您将数据放入一个标题(无
#包含
保护!),然后使用宏有条件地展开它

例子:
Data.h

X(Option1, "Description of option 1", int, long)
X(Option2, "Description of option 2", double, short)
X(Option3, "Description of option 3", wchar_t*, char *)
MyProgram.cpp

enum Options
{
#define X(Option, Description, Arg1, Arg2) Option,
#   include "Data.h"
#undef X
};

char const *descriptions[] =
{
#define X(Option, Description, Arg1, Arg2) Description,
#   include "Data.h"
#undef X
};

#define X(Option, Description, Arg1, Arg2) typedef void (*P##Option)(Arg1, Arg2);
#   include "Data.h"
#undef X

这并不是最漂亮的景象,但它避免了代码重复,让您可以将所有东西都放在一个地方。

是的,它们仍然有类似的用途

例如,这是我的一些个人代码的一部分:

BEGIN_MSG_MAP(This)
    MESSAGE_HANDLER(WM_CLOSE, OnClose)
    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
    MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode)
    COMMAND_RANGE_HANDLER(IDOK, IDNO, OnButtonClick)
    CHAIN_MSG_MAP(CDialogResize<This>)
END_MSG_MAP()

在没有宏的情况下编写此文件将涉及大量不必要的样板代码。

由于宏的特性,这些宏被认为是容易出错的,这里我们有一些容易出错的宏的好例子:

但它们在某些方面可能有用,例如,为了使te:

使用“有用的宏”,您可以像这样调用函数指针:

callMemberFunction(fred,memFn)('x', 3.14);
这比以下几点更清楚:

(fred.*memFn)('x', 3.14);
信用证:。

有几种用途(有些可能已经提到了…)

  • 日志记录:
    DEBUG(…)
    ,这很简洁,因为只有在日志记录处于活动状态时才对内容进行计算(因此宏可以对日志级别进行测试,例如…),您不能将其替换为内联函数,因为参数将始终进行计算。然而,对于c++11,有一个lambda技巧可以避免计算,但是syntnax很笨拙,因此无论如何都需要一个宏来清理它!:)
  • 代码生成,我使用了大量的SFINAE测试,并且很容易用几个宏生成测试,而不是每次手工构造测试
    当您需要利用平台、编译器或特定于实现的功能时。通常,这是为了提高可移植性或访问在目标系统中以不同方式表达的功能(即,在您使用的编译器上可能以不同方式编写)


    扩展Matthieu的答案(+1):断言。

    使用宏编写异常检查测试比使用函数容易得多

    #define TEST_THROWS(code) do { try { code; } catch (...) { pass(); } fail(); } while(0)
    

    注意:示例未测试

    扩展@Matthieu的答案,我使用宏将文件和行日志添加到遗留代码库中。因此:

    void MovePlayer(Vector3 position)
    { 
        ...
    }
    
    变得有点像:

    #define MovePlayer(pos) MovePlayer_(pos, __FILE__, __LINE__)
    
    void MovePlayer_(Vector3 position, const char* file, int line)
    {
        LogFunctionCall("MovePlayer", file, line);
        ...
    }
    

    通过只更改代码库中的一个位置,我就能够记录在复杂测试期间调用函数的任何地方。如果您对足够多的函数执行此操作,它对于跟踪旧代码库中的现有行为非常有用。

    想到基于其他定义的条件typedef(主要是在具有unicode和ansi字符串的windows上)“可移植”(跨平台)库需要它们。复制(通过时间旅行)当然,使用模板绝对是可行的。Sekan库在对有间隙的q-gram执行哈希时使用循环展开模板。@KonradRudolph如果您认为这是可行的,如果您回答链接的问题,我将非常高兴?我很确定这对于这个特殊的问题是不可行的,因为编译时/运行时/切换限制。实际上,这可能可以用C++11中的初始值设定项列表来简化。我实际上会说这是一个糟糕的例子。对你来说,这一点稍微清楚一些,但是任何一个C++程序员都知道<代码> *>代码>,而你只是创建了一个方言。当我看到
    callMemberFunction
    时,我的第一反应是检查实现,以确保它做到了它所说的。
    void MovePlayer(Vector3 position)
    { 
        ...
    }
    
    #define MovePlayer(pos) MovePlayer_(pos, __FILE__, __LINE__)
    
    void MovePlayer_(Vector3 position, const char* file, int line)
    {
        LogFunctionCall("MovePlayer", file, line);
        ...
    }