9

据我所知,宏甚至在编译器正确地看到它之前重新排列程序文本,因此可能会导致问题。我很少在 C++ 代码中看到它们,主要是在 C 中。

我所知道的唯一好的用途是包含保护 ( #ifndef)。

还有什么需要的吗用宏来完成并且不能以更清洁的方式实现的吗?

4

11 回答 11

4

在 C++11 之前,您通常会定义static_assert为宏(typedef如果条件为假,则 a 将无效),因此它可以从任何地方(命名空间级别或函数级别)获得并且仍然不会模棱两可(例如,通过使用行号)。

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

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

于 2013-08-22T07:12:46.070 回答
4

日志记录异常

宏允许您毫不费力地捕捉__FILE__和。哦,当然你每次都可以手动编写它们,但坦率地说,这很乏味且容易出错(两者都是C 字符串,所以你有混淆它们的风险)。__LINE____func____FILE____func__

于 2013-08-22T07:34:32.490 回答
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

这不是最漂亮的景象,但它避免了代码重复,并让您将所有内容保存在一个地方。

于 2013-08-22T07:53:50.183 回答
2

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

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

于 2013-08-22T07:12:25.173 回答
2

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

但它们在某些方面可能很有用,例如,为了使代码在处理函数指针时更具可读性

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

于 2013-08-22T08:07:47.680 回答
2

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

  1. Logging: DEBUG(....),这很简洁,因为只有在日志记录处于活动状态时才会评估内容(例如,宏可以测试日志级别......)您不能将其替换为内联函数,因为始终会评估参数。但是对于 c++11,有一个 lambda 技巧可以避免评估,但是syntnax 是笨拙的,所以你最终需要一个宏来清理它!:)
  2. 代码生成,我使用了很多 SFINAE 测试,使用几个宏很容易生成测试,而不是每次都手动构建测试。
于 2013-08-22T08:22:45.233 回答
1

When you need to utilize platform, compiler, or implementation specific features. Typically, this is either to improve portability or to access features which are expressed differently in the systems you target (i.e. it may be written differently on the compilers you use).

And expanding on Matthieu's answer (+1): Assertions.

于 2013-08-22T08:30:37.680 回答
1

是的,它们仍然会在 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()

在没有宏的情况下编写它会涉及很多不必要的样板代码。

于 2013-08-22T07:59:24.930 回答
1

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

于 2013-08-22T07:12:36.423 回答
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);
    ...
}

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

于 2013-08-22T13:20:28.717 回答
1

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

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

注意:示例未测试

于 2013-08-22T12:37:46.557 回答