C ++macros何时有益?
C预处理器有理由被C ++社区所担心和避免。 内嵌的函数,常量和模板通常是一个更安全,更优越的替代#define
。
以下macros:
#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)
绝不会超越types安全:
inline bool succeeded(int hr) { return hr >= 0; }
但是macros也有它们自己的位置,请列出您find的用于macros的用法,这是您在没有预处理器的情况下无法完成的。
请把每个用例分别放在一个单独的答案中,这样就可以对它进行投票,如果你知道如何在没有预先指出的情况下指出答案的评论如何得到答案之一。
作为debugging函数的包装,要自动传递诸如__FILE__
, __FILE__
__LINE__
等的东西:
#ifdef ( DEBUG ) #define M_DebugLog( msg ) std::cout << __FILE__ << ":" << __LINE__ << ": " << msg #else #define M_DebugLog( msg ) #endif
方法必须始终是完整的,可编译的代码; macros可能是代码片段。 因此你可以定义一个foreachmacros:
#define foreach(list, index) for(index = 0; index < list.size(); index++)
并如此使用它:
foreach(cookies, i) printf("Cookie: %s", cookies[i]);
从C ++ 11开始,这被基于范围的for循环取代。
在条件编译内部,为了克服编译器之间的差异问题:
#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
头文件保护需要macros。
还有其他地方需要macros吗? 没有多less(如果有的话)。
有没有其他的情况可以从macros中受益? 是!!!
我使用macros的一个地方是非常重复的代码。 例如,当包装C ++代码以用于其他接口(.NET,COM,Python等)时,我需要捕获不同types的exception。 以下是我如何做到这一点:
#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__); \ }
我必须把这些捕获在每个包装函数。 每次input完整的catch块,而不是input:
void Foo() { try { ::mylib::Foo() } HANDLE_EXCEPTIONS }
这也使得维护更容易。 如果我不得不添加一个新的exceptiontypes,只有一个地方我需要添加它。
还有其他一些有用的例子:其中很多包括__FILE__
和__LINE__
预处理器macros。
无论如何,macros使用正确时非常有用。 macros不是邪恶 – 它们的滥用是邪恶的。
大多:
- 包括警卫
- 有条件编译
- 报告(预定义的macros如
__LINE__
和__FILE__
) - (很less)重复重复的代码模式。
- 在你的竞争对手的代码。
当你想从一个expression式中创build一个string时,最好的例子是assert
( #x
将x
的值转换为一个string)。
#define ASSERT_THROW(condition) \ if (!(condition)) \ throw std::exception(#condition " is false");
string常量有时更好地定义为macros,因为您可以使用string文字比使用const char *
。
如string文字可以很容易地连接 。
#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 *
,那么必须使用某种types的string来在运行时执行连接:
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);
当您想要更改程序stream( 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 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
像UnitTest ++这样的C ++unit testing框架几乎围绕着预处理macros。 几行unit testing代码展开成类的层次结构,根本无法手动input。 没有像UnitTest ++这样的预处理器魔法,我不知道如何有效地为C ++编写unit testing。
比方说,我们会忽略像标题守卫这样的明显的东西。
有时,您想要生成需要预编译器复制/粘贴的代码:
#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"
请注意,将模板与macros混合可以导致更好的结果(即自动生成与variables名称并排的值)
其他时候,你需要一些代码的__FILE__和/或__LINE__来产生debugging信息,例如。 以下是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; }
(注:代码复制/粘贴从助推首页)
这是(恕我直言)方式比std::for_each
更好。
所以,macros常常是有用的,因为它们超出了正常的编译规则。 但是我发现,大多数时候我看到一个,他们实际上仍然是从未转换成适当的C ++的C代码。
害怕C预处理器就像是因为我们得到荧光灯泡而害怕白炽灯泡。 是的,前者可以是{电| | 程序员时间}效率低下。 是的,你可以(从字面上)烧他们。 但是,如果你能妥善处理,他们可以完成工作。
当你编程embedded式系统时,C用作汇编程序的唯一select。 在用C ++在桌面上编程后,切换到更小的embedded式目标后,您将学会停止担心如此多的裸Cfunction(包括macros)的“无法使用”,并试图找出可以从中获得的最佳和安全的用法特征。
亚历山大·斯捷潘诺夫说 :
当我们用C ++编程时,我们不应该为C的遗产感到羞耻,而是要充分利用它。 C ++的唯一问题,甚至是C的唯一问题,当它们本身不符合自己的逻辑时,就会出现。
我们使用__FILE__
和__LINE__
macros作为诊断目的,在信息丰富的exception抛出,捕获和日志logging中,以及我们的QA基础架构中的自动日志文件扫描程序。
例如,抛出的macrosOUR_OWN_THROW
可能与exceptiontypes和构造函数参数一起使用,包括文本描述。 喜欢这个:
OUR_OWN_THROW(InvalidOperationException, (L"Uninitialized foo!"));
这个macros当然会抛出InvalidOperationException
exception的描述作为构造函数的参数,但是它也会把一个消息写到一个日志文件中,这个文件包括文件名和行号在哪里出现,以及它的文本描述。 抛出的exception会得到一个id,这个id也被logging下来。 如果在代码中的其他地方捕获到exception,它将被标记为这样,并且日志文件将指示该特定的exception已经被处理,并且因此不可能是稍后可能被logging的任何崩溃的原因。 未处理的exception可以很容易地被我们的自动化QA基础架构拾取。
一些非常先进和有用的东西仍然可以使用预处理器(macros)来构build,而使用c ++“语言结构”(包括模板)则永远无法做到这一点。
例子:
制作一个C标识符和一个string
简单的方法在C中使用枚举types的variables作为string
加速预处理器元编程
我偶尔使用macros,所以我可以在一个地方定义信息,但在代码的不同部分以不同的方式使用它。 只是有点邪恶:)
例如,在“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;
而在一个私人的初始化函数中,所有的字段都可以用来填充表中的数据:
#define FIELD(name, desc, value) \ table[FIELD_ ## name].desc = desc; \ table[FIELD_ ## name].value = value; #include "field_list.h"
代码重复。
看一下提升预处理器库 ,这是一种元元编程。 在主题 – >动机中,你可以find一个很好的例子。
一个常见的用途是检测编译环境,对于跨平台开发,当没有跨平台库已经存在用于您的目的时,您可以为linux编写一套代码,另一种用于Windows。
所以,在一个粗略的例子中,跨平台的互斥可以有
void lock() { #ifdef WIN32 EnterCriticalSection(...) #endif #ifdef POSIX pthread_mutex_lock(...) #endif }
对于函数,当你想明确地忽略types安全时,它们是有用的。 比如上面和下面做ASSERT的很多例子。 当然,就像很多C / C ++的function一样,你可以在脚下自己拍摄,但是语言给了你工具,让你决定做什么。
就像是
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);
而不是macros,所有你可以得到的是你的断言function的行号打印到日志,这将不太有用。
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])
与当前线程中讨论的“首选”模板解决scheme不同,您可以将其用作常量expression式:
char src[23]; int dest[ARRAY_SIZE(src)];
您可以使用#defines来帮助debugging和unit testing场景。 例如,创build内存函数的特殊日志logging变体,并创build一个特殊的memlog_preinclude.h:
#define malloc memlog_malloc #define calloc memlog calloc #define free memlog_free
编译你的代码使用:
gcc -Imemlog_preinclude.h ...
将memlog.o链接到最终图像。 您现在可以控制malloc等,也许为了logging目的,或模拟unit testing的分配失败。
编译器可以拒绝你的内联请求。
macros将始终有自己的位置。
我觉得有用的是#define DEBUG,用于debugging跟踪 – 您可以在debugging问题时将其保留为1(甚至在整个开发周期中保持开启状态),然后在发货时将其closures。
编译时通过编译器/操作系统/硬件的特定行为做出决定时。
它使您可以使您的界面到Comppiler / OS /硬件的特定function。
#if defined(MY_OS1) && defined(MY_HARDWARE1) #define MY_ACTION(a,b,c) doSothing_OS1HW1(a,b,c);} #elif define(MY_OS1) && defined(MY_HARDWARE2) #define MY_ACTION(a,b,c) doSomthing_OS1HW2(a,b,c);} #elif define(MY_SUPER_OS) /* On this hardware it is a null operation */ #define MY_ACTION(a,b,c) #else #error "PLEASE DEFINE MY_ACTION() for this Compiler/OS/HArdware configuration" #endif
在我上一份工作中,我正在研究病毒扫描程序。 为了让我更容易进行debugging,我在各处都logging了大量日志logging,但在这样的高需求应用程序中,函数调用的代价太昂贵了。 所以,我想出了这个小的macros,这仍然允许我在一个客户站点的发布版本上启用debugging日志logging,而没有函数调用的代价将检查debugging标志,只是返回没有logging任何东西,或者如果启用,会做logging…macros的定义如下:
#define dbgmsg(_FORMAT, ...) if((debugmsg_flag & 0x00000001) || (debugmsg_flag & 0x80000000)) { log_dbgmsg(_FORMAT, __VA_ARGS__); }
由于日志函数中的VA_ARGS,对于这样的macros来说,这是一个很好的例子。
在此之前,我在一个高度安全的应用程序中使用了一个macros,它需要告诉用户他们没有正确的访问权限,并且会告诉他们他们需要什么标志。
macros(s)定义为:
#define SECURITY_CHECK(lRequiredSecRoles) if(!DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, true)) return #define SECURITY_CHECK_QUIET(lRequiredSecRoles) (DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, false))
然后,我们可以在整个UI上撒上检查,并且告诉你哪些angular色被允许执行你想要做的动作,如果你还没有这个angular色的话。 其中两个原因是为了在某些地方返回一个值,并在其他地方从一个无效函数返回。
SECURITY_CHECK(ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR); LRESULT CAddPerson1::OnWizardNext() { if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_EMPLOYEE) { SECURITY_CHECK(ROLE_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD ) -1; } else if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_CONTINGENT) { SECURITY_CHECK(ROLE_CONTINGENT_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR) -1; } ...
无论如何,这就是我使用它们的方式,而且我不确定这可以如何帮助模板…除此之外,我试图避免它们,除非真的有必要。
又一个foreachmacros。 T:type,c:container,i:iterator
#define foreach(T, c, i) for(T::iterator i=(c).begin(); i!=(c).end(); ++i) #define foreach_const(T, c, i) for(T::const_iterator i=(c).begin(); i!=(c).end(); ++i)
用法(概念显示,不是真实的):
void MultiplyEveryElementInList(std::list<int>& ints, int mul) { foreach(std::list<int>, ints, i) (*i) *= mul; } int GetSumOfList(const std::list<int>& ints) { int ret = 0; foreach_const(std::list<int>, ints, i) ret += *i; return ret; }
更好的实现可用:Google “BOOST_FOREACH”
好文章: 有条件的爱:FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html
也许macros的使用是在平台无关的发展。 考虑types不一致的情况 – 使用macros,可以简单地使用不同的头文件 – 例如:–WIN_TYPES.H
typedef ...some struct
–POSIX_TYPES.h
typedef ...some another struct
–program.h
#ifdef WIN32 #define TYPES_H "WINTYPES.H" #else #define TYPES_H "POSIX_TYPES.H" #endif #include TYPES_H
对我来说,比其他方式来实现它更可读。
我使用macros来轻松定义exception:
DEF_EXCEPTION(RessourceNotFound, "Ressource not found")
DEF_EXCEPTION是
#define DEF_EXCEPTION(A, B) class A : public exception\ {\ public:\ virtual const char* what() const throw()\ {\ return B;\ };\ }\
似乎VA_ARGS只是间接提到:
在编写通用C ++ 03代码时,您需要可变数量的(通用)参数,您可以使用macros而不是模板。
#define CALL_RETURN_WRAPPER(FnType, FName, ...) \ if( FnType theFunction = get_op_from_name(FName) ) { \ return theFunction(__VA_ARGS__); \ } else { \ throw invalid_function_name(FName); \ } \ /**/
注意:通常,名称检查/抛出也可以包含在假设的get_op_from_name
函数中。 这只是一个例子。 围绕VA_ARGS调用可能还有其他的通用代码。
一旦我们用C ++ 11获得可变参数模板,我们可以用模板“正确地”解决这个问题。
我认为这个技巧巧妙地使用了无法用函数模拟的预处理器:
#define COMMENT COMMENT_SLASH(/) #define COMMENT_SLASH(s) /##s #if defined _DEBUG #define DEBUG_ONLY #else #define DEBUG_ONLY COMMENT #endif
那么你可以像这样使用它:
cout <<"Hello, World!" <<endl; DEBUG_ONLY cout <<"This is outputed only in debug mode" <<endl;
您也可以定义一个RELEASE_ONLYmacros。
您可以使用-D
或/D
选项在编译器命令行上定义常量。 当为多个平台交叉编译相同的软件时,这样做通常很有用,因为您可以让makefile控制为每个平台定义的常量。