宏有什么好用吗
Are there any good uses of macros?
据我所知,宏在编译器正确看到程序文本之前就对程序文本进行了重新排列,因此可能会引发问题。我几乎从未在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。
几种用途(有些可能已经提到了…)
- 日志记录:
DEBUG(....)
,这很好,因为只有当日志记录处于活动状态时才会计算内容(因此宏可以对日志级别进行测试,例如…)您不能将其替换为内联函数,因为参数将始终进行计算。然而,对于c++11,有一个lambda技巧可以避免求值,然而syntnax是包含的,所以你最终需要一个宏来清理它!:) - 代码生成,我使用了很多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);
...
}
通过只更改代码库中的一个位置,我就可以记录在复杂测试中调用该函数的所有位置。如果对足够多的函数执行此操作,那么它对于跟踪旧代码库中的现有行为非常有用。
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 警告处理为错误这里有什么问题
- 什么时候调用组成单元对象的析构函数
- #定义c-预处理器常量..我做错了什么
- 努力将整数转换为链表。不知道我在这里做错了什么
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 什么时候在C++中返回常量引用是个好主意
- 当在同一名称空间中有两个具有相同签名的函数时,会发生什么
- C++避免重复声明的语法是什么
- c++库的公共头文件中应该包含什么
- 问题:什么是QAbstractItemView::NoEditTriggers的反面
- 有什么方法可以遍历结构吗
- 当类在C++中定义时,有什么方法可以"register"类吗?
- ifstream什么都没读
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- C++从另一个类访问公共静态向量的正确方法是什么
- "throw expression code" 1e7 >返回 d 是什么?投掷标准::overflow_error( "too big" ) : d;意味 着?
- 我应该使用什么来代替void作为变体中的替代类型之一
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用