宏有什么好的用途吗?

时间:2013-08-22 07:04:56

标签: c++ macros

据我所知,宏在编译器正确看到之前重新排列了程序文本,从而可能导致问题。我几乎没有在C ++代码中看到它们,主要是在C语言中。

我所知道的唯一好处是包含警卫(#ifndef)。

是否还有其他需要要完成宏并且无法以更清晰的方式实现?

11 个答案:

答案 0 :(得分:4)

在C ++ 11之前,您通常会将static_assert定义为宏(如果条件为false,则typedef将无效),因此可以从任何位置(命名空间级别)使用它,或功能级别),但仍然不明确(例如,使用行号)。

Boost.Preprocessor library是使用宏来减少高度冗余代码量的另一个很好的例子,另一个与可变参数模板不太相关的例子。

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

答案 1 :(得分:4)

记录例外

宏可让您毫不费力地捕获__FILE____LINE____func__。哦,确定你每次都可以手动编写它们,但坦率地说这很乏味且容易出错(__FILE____func__都是C字符串,所以你冒险将它们混合起来。)

答案 2 :(得分:3)

是的,X-macro技巧总是有用的。

您将数据放在一个标题中(没有#include警卫!),然后使用宏来有条件地扩展它。

实施例

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

这不是最漂亮的视线,但它避免了代码重复,让你把所有东西放在一个地方。

答案 3 :(得分:2)

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

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

答案 4 :(得分:2)

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

但它们在某些方面可能有用,例如,为了制作te code more readable when dealing with function pointers

class Fred {
public:
    int f(char x, float y);
    int g(char x, float y);
    int h(char x, float y);
    int i(char x, float y);
    // ...
};

// FredMemberFn points to a member of Fred that takes (char,float)
typedef  int (Fred::*FredMemberFn)(char x, float y);

// Useful macro:
#define callMemberFunction(object,ptrToMember)  ((object).*(ptrToMember))

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

callMemberFunction(fred,memFn)('x', 3.14);

稍微清楚一点:

(fred.*memFn)('x', 3.14);

致谢:C++ FAQ Lite

答案 5 :(得分:2)

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

  1. 记录:DEBUG(....),这很简洁,因为只有在日志记录处于活动状态时才评估内容(因此宏可以测试日志级别,例如...)您不能用内联函数替换它因为参数将始终被评估。但是对于c ++ 11,有一个lambda技巧可以避免评估,然而这个合成器是cludgy所以你最终还是需要一个宏来清理它! :)
  2. 代码生成,我使用了大量的SFINAE测试,并且很容易使用几个宏生成测试,而不是每次都手工构建测试。

答案 6 :(得分:1)

有一些代码重写用于优化,似乎不适用于模板元编程并需要宏。以下是一个可能的示例:C++ template for unrolling a loop using a switch?

答案 7 :(得分:1)

是的,它们仍然会用于"message maps" in ATL, WTL, MFC

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

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()

甚至指定窗口的布局:

BEGIN_DLGRESIZE_MAP(This)
    DLGRESIZE_CONTROL(IDOK, DLSZ_MOVE_X)
    DLGRESIZE_CONTROL(IDCANCEL, DLSZ_MOVE_X)
    DLGRESIZE_CONTROL(IDC_EDITINPUT, DLSZ_SIZE_X)
END_DLGRESIZE_MAP()

在没有宏的情况下编写这将涉及许多不必要的样板代码。

答案 8 :(得分:1)

当您需要利用平台,编译器或实现特定功能时。通常,这要么是为了提高可移植性,要么是为了访问在您定位的系统中表达方式不同的功能(即可能在您使用的编译器上以不同方式编写)。

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

答案 9 :(得分:1)

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

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

注意:示例未经过测试

答案 10 :(得分:1)

根据@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);
    ...
}

通过仅更改代码库中的一个位置,我能够记录在复杂测试期间调用该函数的所有位置。如果对足够的函数执行此操作,则对于跟踪旧代码库中的现有行为非常有用。