该ç预处理器有理由担心和C++社区避之唯恐不及.内联功能,功能和模板通常是更安全和更好的替代品#define
.
以下宏:
#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)
绝不是优于安全类型:
inline bool succeeded(int hr) { return hr >= 0; }
但宏确实有它们的位置,请列出您为宏找到的用途,如果没有预处理器,您就无法做到.
请将每个用例放在一个单独的答案中,以便可以进行投票,如果您知道如何在没有预备教授的情况下实现其中一个答案,请指出该答案的评论.
作为包装的调试功能,以自动传递之类的东西__FILE__
,__LINE__
等:
#ifdef ( DEBUG ) #define M_DebugLog( msg ) std::cout << __FILE__ << ":" << __LINE__ << ": " << msg #else #define M_DebugLog( msg ) #endif
方法必须始终是完整的,可编译的代码; 宏可能是代码片段.因此,您可以定义一个foreach宏:
#define foreach(list, index) for(index = 0; index < list.size(); index++)
并因此使用它:
foreach(cookies, i) printf("Cookie: %s", cookies[i]);
从C++ 11开始,它被基于范围的for循环所取代.
头文件保护需要宏.
还有其他需要宏的领域吗?不多(如果有的话).
是否有其他情况可以从宏中受益?是!!!
我使用宏的一个地方是非常重复的代码.例如,当包装C++代码以与其他接口(.NET,COM,Python等)一起使用时,我需要捕获不同类型的异常.我是这样做的:
#define HANDLE_EXCEPTIONS \ catch (::mylib::exception& e) { \ throw gcnew MyDotNetLib::Exception(e); \ } \ catch (::std::exception& e) { \ throw gcnew MyDotNetLib::Exception(e, __LINE__, __FILE__); \ } \ catch (...) { \ throw gcnew MyDotNetLib::UnknownException(__LINE__, __FILE__); \ }
我必须把这些捕获物放在每个包装函数中.我不是每次都输入完整的catch块,而是输入:
void Foo() { try { ::mylib::Foo() } HANDLE_EXCEPTIONS }
这也使维护更容易.如果我必须添加一个新的异常类型,我只需要添加一个地方.
还有其他有用的示例:其中许多包括__FILE__
和__LINE__
预处理器宏.
无论如何,正确使用时,宏非常有用.宏不是邪恶的 - 他们的滥用是邪恶的.
大多:
包括警卫
条件编译
报告(预定义的宏像__LINE__
和__FILE__
)
(很少)复制重复的代码模式.
在您的竞争对手的代码中.
在条件编译中,要克服编译器之间的差异问题:
#ifdef ARE_WE_ON_WIN32 #define close(parm1) _close (parm1) #define rmdir(parm1) _rmdir (parm1) #define mkdir(parm1, parm2) _mkdir (parm1) #define access(parm1, parm2) _access(parm1, parm2) #define create(parm1, parm2) _creat (parm1, parm2) #define unlink(parm1) _unlink(parm1) #endif
#ifdef ARE_WE_ON_WIN32
inline int close(int i){return _close(i); } #endif code>
这将删除#define,但不会删除#ifdef和#endif.无论如何,我同意你的看法.
6> Motti..:
当你想从表达式中创建一个字符串时,最好的例子是assert
(#x
将值x
转换为字符串).
#define ASSERT_THROW(condition) \
if (!(condition)) \
throw std::exception(#condition " is false");
我同意,事实上我会把它放在do {} while(false)(以防止其他劫持)但我想保持简单.
只是一个挑剔,但我个人会离开分号.
7> Motti..:
字符串常量有时更好地定义为宏,因为您可以使用字符串文字而不是使用字符串文字const char *
.
例如,字符串文字可以很容易地连接起来.
#define BASE_HKEY "Software\\Microsoft\\Internet Explorer\\"
// Now we can concat with other literals
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "Settings", &settings);
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "TypedURLs", &URLs);
如果使用了a const char *
,那么必须使用某种字符串类来在运行时执行连接:
const char* BaseHkey = "Software\\Microsoft\\Internet Explorer\\";
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "Settings").c_str(), &settings);
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "TypedURLs").c_str(), &URLs);
在C++ 11中,我认为这是最重要的部分(除了包含警卫).宏是我们编译时字符串处理的最佳选择.这是我希望我们在C++ 11 ++中得到的功能
我希望我能+42这个.字符串文字的一个非常重要但不常被记住的方面.
8> Motti..:
当你想改变程序流(return
,break
和continue
)在功能代码的行为相比,在功能其实内联代码不同.
#define ASSERT_RETURN(condition, ret_val) \
if (!(condition)) { \
assert(false && #condition); \
return ret_val; }
// should really be in a do { } while(false) but that's another discussion.
9> Kena..:
显而易见的是警卫
#ifndef MYHEADER_H
#define MYHEADER_H
...
#endif
10> 1800 INFORMA..:
您不能使用常规函数调用执行函数调用参数的短路.例如:
#define andm(a, b) (a) && (b)
bool andf(bool a, bool b) { return a && b; }
andm(x, y) // short circuits the operator so if x is false, y would not be evaluated
andf(x, y) // y will always be evaluated
隐藏功能样式宏背后的短路操作是我真正不希望在生产代码中看到的事情之一.
也许更一般的观点:函数只评估一次他们的参数.宏可以更多次或更少次地评估参数.
11> Joe..:
像UnitTest ++这样的C++单元测试框架几乎围绕预处理器宏.几行单元测试代码扩展为类的层次结构,根本无法手动输入.没有像UnitTest ++这样的预处理器魔法,我不知道你如何有效地为C++编写单元测试.
12> paercebal..:
假设我们会忽略像标题守卫这样的显而易见的事情.
有时,您希望生成需要由预编译器复制/粘贴的代码:
#define RAISE_ERROR_STL(p_strMessage) \
do \
{ \
try \
{ \
std::tstringstream strBuffer ; \
strBuffer << p_strMessage ; \
strMessage = strBuffer.str() ; \
raiseSomeAlert(__FILE__, __FUNCSIG__, __LINE__, strBuffer.str().c_str()) \
} \
catch(...){} \
{ \
} \
} \
while(false)
这使您可以编码:
RAISE_ERROR_STL("Hello... The following values " << i << " and " << j << " are wrong") ;
并且可以生成以下消息:
Error Raised:
====================================
File : MyFile.cpp, line 225
Function : MyFunction(int, double)
Message : "Hello... The following values 23 and 12 are wrong"
请注意,将模板与宏混合可以获得更好的结果(即,使用变量名称自动生成值)
其他时候,您需要某些代码的__FILE__和/或__LINE__来生成调试信息.以下是Visual C++的经典之作:
#define WRNG_PRIVATE_STR2(z) #z
#define WRNG_PRIVATE_STR1(x) WRNG_PRIVATE_STR2(x)
#define WRNG __FILE__ "("WRNG_PRIVATE_STR1(__LINE__)") : ------------ : "
与以下代码一样:
#pragma message(WRNG "Hello World")
它生成如下消息:
C:\my_project\my_cpp_file.cpp (225) : ------------ Hello World
其他时候,您需要使用#和##连接运算符生成代码,例如为属性生成getter和setter(这是非常有限的情况,通过).
其他时候,如果通过函数使用,您将生成不会编译的代码,例如:
#define MY_TRY try{
#define MY_CATCH } catch(...) {
#define MY_END_TRY }
哪个可以用作
MY_TRY
doSomethingDangerous() ;
MY_CATCH
tryToRecoverEvenWithoutMeaningfullInfo() ;
damnThoseMacros() ;
MY_END_TRY
(仍然,我只看到这种代码正确使用过一次)
最后,但并非最不重要的,着名boost::foreach
!!!
#include
#include
#include
int main()
{
std::string hello( "Hello, world!" );
BOOST_FOREACH( char ch, hello )
{
std::cout << ch;
}
return 0;
}
(注意:代码从升级主页复制/粘贴)
这是(恕我直言)的方式比std::for_each
.
因此,宏总是有用的,因为它们超出了正常的编译器规则.但我发现大多数时候我看到它们,它们实际上仍然是C代码,从未转换成适当的C++.
13> 小智..:
担心C预处理器就像是因为我们得到荧光灯而害怕白炽灯泡.是的,前者可以是{电| 程序员时间效率低下.是的,你可以(字面上)被他们烧掉.但如果你妥善处理它们,他们可以完成工作.
编程嵌入式系统时,C使用除了表单汇编程序之外的唯一选项.在使用C++在桌面上编程然后切换到较小的嵌入式目标之后,您将学会不再担心如此多的裸C功能(包括宏)的"无效",并试图找出从这些功能中获得的最佳和安全的用法特征.
Alexander Stepanov 说:
当我们用C++编程时,我们不应该为它的C传承感到羞耻,而是要充分利用它.C++的唯一问题,甚至是C的唯一问题,都出现在它们自身与自己的逻辑不一致时.
14> Johann Gerel..:
我们在信息丰富的异常抛出,捕获和日志记录以及我们的QA基础架构中的自动日志文件扫描程序中使用__FILE__
和__LINE__
宏进行诊断.
例如,抛出宏OUR_OWN_THROW
可能与异常的异常类型和构造函数参数一起使用,包括文本描述.像这样:
OUR_OWN_THROW(InvalidOperationException, (L"Uninitialized foo!"));
这个宏当然会将InvalidOperationException
描述作为构造函数参数抛出异常,但它也会将消息写入日志文件,该文件由发生抛出的文件名和行号及其文本描述组成.抛出的异常将获得一个id,它也会被记录.如果异常被捕获到代码中的其他位置,它将被标记为这样,并且日志文件将指示已经处理了该特定异常,因此它不可能是稍后可能记录的任何崩溃的原因.我们的自动化QA基础架构可以轻松获取未处理的异常.
15> Suma..:
一些非常高级和有用的东西仍然可以使用预处理器(宏)构建,使用包含模板的c ++"语言构造",你永远无法做到这一点.
例子:
制作C标识符和字符串
在C中使用枚举类型变量作为字符串的简单方法
提升预处理器元编程
16> Ruggero Turr..:
代码重复.
看看如何增强预处理器库,它是一种元元编程.在topic-> motiv中你可以找到一个很好的例子.
17> Andrew Johns..:
我偶尔会使用宏,因此我可以在一个地方定义信息,但在代码的不同部分以不同的方式使用它.它只是有点邪恶:)
例如,在"field_list.h"中:
/*
* List of fields, names and values.
*/
FIELD(EXAMPLE1, "first example", 10)
FIELD(EXAMPLE2, "second example", 96)
FIELD(ANOTHER, "more stuff", 32)
...
#undef FIELD
然后对于公共枚举,可以将其定义为仅使用名称:
#define FIELD(name, desc, value) FIELD_ ## name,
typedef field_ {
#include "field_list.h"
FIELD_MAX
} field_en;
在私有init函数中,所有字段都可用于使用数据填充表:
#define FIELD(name, desc, value) \
table[FIELD_ ## name].desc = desc; \
table[FIELD_ ## name].value = value;
#include "field_list.h"
18> Doug T...:
一个常见的用途是检测编译环境,对于跨平台开发,您可以编写一组代码用于Linux,另一个用于Windows,当没有用于您的目的的跨平台库时.
因此,在一个粗略的例子中,跨平台互斥体可以拥有
void lock()
{
#ifdef WIN32
EnterCriticalSection(...)
#endif
#ifdef POSIX
pthread_mutex_lock(...)
#endif
}
对于函数,当您想明确忽略类型安全时,它们很有用.例如上面和下面的许多例子用于做ASSERT.当然,像许多C/C++功能一样,你可以自己动手,但语言为你提供了工具,让你决定做什么.
19> 小智..:
就像是
void debugAssert(bool val, const char* file, int lineNumber);
#define assert(x) debugAssert(x,__FILE__,__LINE__);
所以你可以举例说
assert(n == true);
如果n为false,则获取打印到日志中的问题的源文件名和行号.
如果你使用正常的函数调用,如
void assert(bool val);
而不是宏,你可以获得的是你的断言函数的行号打印到日志,这将没那么有用.