宏有什么好用吗

Are there any good uses of macros?

本文关键字:什么      更新时间:2023-10-16

据我所知,宏在编译器正确看到程序文本之前就对程序文本进行了重新排列,因此可能会引发问题。我几乎从未在C++代码中看到过它们,主要是在C.中

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

是否还需要使用宏执行其他操作,并且无法以更干净的方式实现?

日志记录异常

使用宏可以轻松地捕获__FILE____LINE____func__。哦,当然你每次都可以手动编写它们,但坦率地说,这既乏味又容易出错(__FILE____func__都是C字符串,所以你有把它们混淆的风险)。

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

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

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

是的,X宏技巧总是很有用的

您将数据放入一个标头中(没有#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

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

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

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

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

  • 宏VS内联函数
  • 包含if的宏
  • 多行宏
  • 连接宏

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

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。

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

  1. 日志记录:DEBUG(....),这很好,因为只有当日志记录处于活动状态时才会计算内容(因此宏可以对日志级别进行测试,例如…)您不能将其替换为内联函数,因为参数将始终进行计算。然而,对于c++11,有一个lambda技巧可以避免求值,然而syntnax是包含的,所以你最终需要一个宏来清理它!:)
  2. 代码生成,我使用了很多SFINAE测试,而且用几个宏生成测试很容易,而不是每次都手工构建测试

有些代码重写用于优化,但模板元编程似乎不可行,需要宏。这里有一个可能的例子:使用开关展开循环的C++模板?

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

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

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

并扩展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);
    ...
}

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