下面是宏:
#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)
并不比类型安全更优越:
inline bool succeeded(int hr) { return hr >= 0; }
但是宏确实有其用处,请列出您发现需要预处理器无法完成的宏使用情况。
请将每个用例放在单独的答案中,以便进行投票。如果您知道如何在没有预处理器的情况下实现其中的某个答案,请在该答案的评论中指出。
#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
std::source_location
可以代替__LINE__
和__FILE__
来实现一个普通函数(模板)的类似功能。__FILE__
和 __LINE__
也 需要预处理器。在你的代码中使用它们就像是预处理器的感染向量。 - T.E.D.方法必须始终是可编译的完整代码;宏可以是代码片段。因此,您可以定义一个foreach宏:
#define foreach(list, index) for(index = 0; index < list.size(); index++)
然后这样使用它:
foreach(cookies, i)
printf("Cookie: %s", cookies[i]);
自C++11以来,这被基于范围的for循环所取代。
for_each
是一件麻烦的事情,因为每个元素运行的代码不属于调用点的本地范围。foreach
(我强烈建议使用BOOST_FOREACH
而不是手动编写解决方案)可以让你将代码保持靠近迭代位置,使其更易读。话虽如此,一旦lambda开始使用,for_each
可能再次成为最佳选择。 - GManNickG头文件保护需要使用宏。
还有其他领域需要使用宏吗?没有多少(如果有的话)。
还有哪些情况可以从宏中受益呢?当然可以!
我使用宏的一个场景是处理非常重复的代码。例如,将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__
预处理器宏。
总之,宏在正确使用时非常有用。宏本身并不邪恶——它们的滥用才是邪恶的。
#pragma once
,所以我怀疑守卫是否真的必要。 - 1800 INFORMATION#pragma once
在许多常见的构建系统上会出现问题。 - Miles Routvoid handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }
。在函数方面:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
。 - MikeMBstd::invoke
的函数模板,并在其中处理异常。基本上,这是一个高阶函数,调用提供的函数并处理任何错误。 - Xeverous主要包括:
__LINE__
和__FILE__
)#ifdef _WIN32 /* 做Windows的事情 */ #elif __linux__ /* 做Linux的事情 */ #elif __APPLE__ /* 做Macintosh的事情 */ #else #error 不支持的操作系统 #endif
- Eljay在条件编译中,为了克服不同编译器之间的差异问题:
#ifdef WE_ARE_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
当你想将一个表达式转换为字符串时,最好的例子是 assert
(#x
将变量x
的值转换为字符串)。
#define ASSERT_THROW(condition) \
if (!(condition)) \
throw std::exception(#condition " is false");
有时候,字符串常量最好定义为宏,因为你可以对字符串字面量做更多的操作,而不是使用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);
如果使用了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++20 开始,可以实现一种类似于字符串的类类型,可用作用户定义的字符串字面量运算符的非类型模板参数类型,从而允许进行编译时的连接操作而无需使用宏。
当您想要更改程序流程(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.
显而易见的包含保护
#ifndef MYHEADER_H
#define MYHEADER_H
...
#endif
假设我们忽略像头文件保护这样的显而易见的事情。
有时候,您需要生成需要由预编译器复制/粘贴的代码:
#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 <string>
#include <iostream>
#include <boost/foreach.hpp>
int main()
{
std::string hello( "Hello, world!" );
BOOST_FOREACH( char ch, hello )
{
std::cout << ch;
}
return 0;
}
(Note: code copy/pasted from the boost homepage)std::for_each
要好得多。#include
#include
using namespace std;
void trace(char const * file, int line, ostream & o) {
cerr<(o).str().c_str()<这样,宏定义代码就会变得更短。
- Rainer Blome