据我所知,宏在编译器正确看到之前重新排列了程序文本,从而可能导致问题。我几乎没有在C ++代码中看到它们,主要是在C语言中。
我所知道的唯一好处是包含警卫(#ifndef
)。
是否还有其他需要要完成宏并且无法以更清晰的方式实现?
答案 0 :(得分:4)
在C ++ 11之前,您通常会将static_assert
定义为宏(如果条件为false,则typedef
将无效),因此可以从任何位置(命名空间级别)使用它,或功能级别),但仍然不明确(例如,使用行号)。
Boost.Preprocessor library是使用宏来减少高度冗余代码量的另一个很好的例子,另一个与可变参数模板不太相关的例子。
此外,宏被广泛用于与编译器“对话”,例如,检查运行的编译器,编译器的版本,C ++ 11支持是否可用等等。
答案 1 :(得分:4)
记录和例外。
宏可让您毫不费力地捕获__FILE__
,__LINE__
和__func__
。哦,确定你每次都可以手动编写它们,但坦率地说这很乏味且容易出错(__FILE__
和__func__
都是C字符串,所以你冒险将它们混合起来。)
答案 2 :(得分:3)
您将数据放在一个标题中(没有#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)
有几种用途(有些可能已经提到过了......)
DEBUG(....)
,这很简洁,因为只有在日志记录处于活动状态时才评估内容(因此宏可以测试日志级别,例如...)您不能用内联函数替换它因为参数将始终被评估。但是对于c ++ 11,有一个lambda技巧可以避免评估,然而这个合成器是cludgy所以你最终还是需要一个宏来清理它! :)答案 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);
...
}
通过仅更改代码库中的一个位置,我能够记录在复杂测试期间调用该函数的所有位置。如果对足够的函数执行此操作,则对于跟踪旧代码库中的现有行为非常有用。