C 预处理器有理由担心并被C ++社区所避免。内联函数,函数和模板通常比#define
更安全,更优越。
以下宏:
#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)
绝不优于类型安全:
inline bool succeeded(int hr) { return hr >= 0; }
但宏确实有它们的位置,请列出您在没有预处理器的情况下无法执行的宏的用途。
请将每个用例分成单独的答案,以便对其进行表决,如果您知道如何在没有预备教授的情况下实现其中一个答案,请指出该答案的评论内容。
答案 0 :(得分:119)
作为调试函数的包装器,自动传递__FILE__
,__LINE__
等内容:
#ifdef ( DEBUG )
#define M_DebugLog( msg ) std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif
答案 1 :(得分:90)
方法必须始终是完整的,可编译的代码;宏可能是代码片段。因此,您可以定义一个foreach宏:
#define foreach(list, index) for(index = 0; index < list.size(); index++)
并按原样使用它:
foreach(cookies, i)
printf("Cookie: %s", cookies[i]);
从C ++ 11开始,它被range-based for loop取代。
答案 2 :(得分:58)
头文件保护需要宏。
是否有其他必须宏的区域?不多(如果有的话)。
是否有其他情况可以从宏中受益? YES !!!
我使用宏的一个地方是非常重复的代码。例如,当包装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__
预处理器宏。
无论如何,正确使用时,宏非常有用。宏不是邪恶的 - 他们的滥用是邪恶的。
答案 3 :(得分:52)
晴:
__LINE__
和__FILE__
)答案 4 :(得分:50)
内部条件编译,以克服编译器之间的差异问题:
#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
答案 5 :(得分:37)
如果要从表达式中创建字符串,最好的示例是assert
(#x
将x
的值转换为字符串)。
#define ASSERT_THROW(condition) \
if (!(condition)) \
throw std::exception(#condition " is false");
答案 6 :(得分:30)
字符串常量有时被更好地定义为宏,因为您可以使用字符串文字而不是const char *
来执行更多操作。
e.g。字符串文字可以是easily concatenated。
#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);
答案 7 :(得分:24)
当您想要更改程序流程(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.
答案 8 :(得分:20)
明显包括警卫
#ifndef MYHEADER_H
#define MYHEADER_H
...
#endif
答案 9 :(得分:17)
使用常规函数调用不能执行函数调用参数的短路。例如:
#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
答案 10 :(得分:16)
假设我们会忽略像标题警卫那样的显而易见的事情。
有时,您希望生成需要由预编译器复制/粘贴的代码:
#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;
}
(注意:代码从升级主页复制/粘贴)
哪个(恕我直言)比std::for_each
更好。
因此,宏总是有用的,因为它们不在正常的编译器规则之内。但我发现大多数时候我看到它们,它们实际上仍然是C代码,从未转换成适当的C ++。
答案 11 :(得分:16)
像UnitTest++这样的C ++的单元测试框架几乎围绕预处理器宏。几行单元测试代码扩展为类的层次结构,根本无法手动输入。没有像UnitTest ++这样的预处理器魔法,我不知道你如何有效地为C ++编写单元测试。
答案 12 :(得分:14)
担心C预处理器就像是因为我们得到荧光灯而害怕白炽灯泡。是的,前者可以是{电|程序员时间效率低下。是的,你可以(字面上)被他们烧掉。但如果你妥善处理它们,他们可以完成工作。
编写嵌入式系统时,C使用除了表单汇编程序之外的唯一选项。在使用C ++在桌面上编程然后切换到较小的嵌入式目标之后,您将学会停止担心如此多的裸C功能(包括宏)的“无效”,并试图找出可以从这些功能获得的最佳和安全的使用方法特征
Alexander Stepanov says:
当我们用C ++编程时,我们不应该为它的C传承感到羞耻,而是要做 充分利用它。出现了C ++的唯一问题,甚至是C的唯一问题 当他们自己不符合自己的逻辑时。
答案 13 :(得分:9)
我们在信息丰富的异常抛出,捕获和日志记录中使用__FILE__
和__LINE__
宏进行诊断,并在QA基础架构中使用自动日志文件扫描程序。
例如,抛出宏OUR_OWN_THROW
可能与该异常的异常类型和构造函数参数一起使用,包括文本描述。像这样:
OUR_OWN_THROW(InvalidOperationException, (L"Uninitialized foo!"));
这个宏当然会将InvalidOperationException
异常与描述作为构造函数参数抛出,但它也会将一条消息写入一个日志文件,该文件由发生抛出的文件名和行号及其文本组成。描述。抛出的异常将获得一个id,它也会被记录。如果异常被捕获到代码中的其他位置,它将被标记为,并且日志文件将指示已经处理了该特定异常,因此它不可能是稍后可能记录的任何崩溃的原因。我们的自动化QA基础架构可以轻松获取未处理的异常。
答案 14 :(得分:9)
代码重复。
看看boost preprocessor library,这是一种元元编程。在topic-&gt;动机中,您可以找到一个很好的例子。
答案 15 :(得分:8)
仍然可以使用预处理器(宏)构建一些非常高级和有用的东西,使用包含模板的c ++“语言结构”,你永远无法做到这一点。
示例:
Making something both a C identifier and a string
答案 16 :(得分:7)
我偶尔会使用宏,因此我可以在一个地方定义信息,但在代码的不同部分以不同的方式使用它。这只是一点点邪恶:)
例如,在“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"
答案 17 :(得分:6)
像
这样的东西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);
而不是宏,你可以得到的是你的断言函数的行号打印到日志中,这样就不那么有用了。
答案 18 :(得分:6)
一个常见的用途是检测编译环境,对于跨平台开发,你可以编写一组代码用于linux,比如说,当没有跨平台库已经存在的时候用于windows。
因此,在一个粗略的例子中,跨平台互斥体可以有
void lock()
{
#ifdef WIN32
EnterCriticalSection(...)
#endif
#ifdef POSIX
pthread_mutex_lock(...)
#endif
}
对于函数,当您想明确忽略类型安全时,它们很有用。例如上面和下面的许多例子用于做ASSERT。当然,就像许多C / C ++功能一样,你可以用脚射击自己,但语言为你提供了工具,让你决定做什么。
答案 19 :(得分:4)
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])
与当前线程中讨论的“首选”模板解决方案不同,您可以将其用作常量表达式:
char src[23];
int dest[ARRAY_SIZE(src)];
答案 20 :(得分:3)
您可以使用#defines来帮助调试和单元测试场景。例如,创建内存函数的特殊日志记录变体并创建一个特殊的memlog_preinclude.h:
#define malloc memlog_malloc
#define calloc memlog calloc
#define free memlog_free
使用以下代码编译代码:
gcc -Imemlog_preinclude.h ...
memlog.o中的链接到最终图像。您现在可以控制malloc等,可能用于记录目的,也可以模拟单元测试的分配失败。
答案 21 :(得分:3)
我使用宏来轻松定义例外:
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;\
};\
}\
答案 22 :(得分:2)
当您在编译时通过编译器/ OS /硬件特定行为做出决定。
它允许您创建与Comppiler / OS / Hardware特定功能的接口。
#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
答案 23 :(得分:2)
也许宏的使用是在平台无关的开发中。 考虑类型不一致的情况 - 使用宏,您可以简单地使用不同的头文件 - 例如: --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
比我在其他方面实施它更具可读性。
答案 24 :(得分:2)
另一个foreach宏。 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
答案 25 :(得分:2)
编译器可以拒绝您的内联请求。
宏将始终占有一席之地。
我觉得有用的东西是#define DEBUG用于调试跟踪 - 你可以在调试问题时将其保留1(或者甚至在整个开发周期中保持打开状态),然后在发货时将其关闭。
答案 26 :(得分:2)
在我上一份工作中,我正在研究病毒扫描程序。为了让我更容易调试,我在整个地方都有很多日志记录,但在这样的高需求应用程序中,函数调用的费用太贵了。所以,我想出了这个小宏,它仍然允许我在客户站点上启用发布版本的调试日志记录,而不需要调用函数调用就会检查调试标志并返回而不记录任何内容,或者如果启用,会进行记录......宏的定义如下:
#define dbgmsg(_FORMAT, ...) if((debugmsg_flag & 0x00000001) || (debugmsg_flag & 0x80000000)) { log_dbgmsg(_FORMAT, __VA_ARGS__); }
由于日志函数中的VA_ARGS,对于像这样的宏来说这是一个很好的例子。
在此之前,我在高安全性应用程序中使用了一个宏,它需要告诉用户他们没有正确的访问权限,并且会告诉他们他们需要什么标记。
宏定义为:
#define SECURITY_CHECK(lRequiredSecRoles) if(!DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, true)) return
#define SECURITY_CHECK_QUIET(lRequiredSecRoles) (DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, false))
然后,我们可以在整个UI上撒上支票,它会告诉您哪些角色可以执行您尝试执行的操作,如果您还没有该角色。其中两个的原因是在某些地方返回一个值,并从其他地方的void函数返回......
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;
}
...
无论如何,这就是我如何使用它们,而且我不确定这对模板有什么帮助...除此之外,我尽量避免它们,除非真的有必要。
答案 27 :(得分:2)
似乎VA_ARGS到目前为止只是间接提到过:
编写通用C ++ 03代码时,如果需要可变数量的(通用)参数,则可以使用宏而不是模板。
#define CALL_RETURN_WRAPPER(FnType, FName, ...) \
if( FnType theFunction = get_op_from_name(FName) ) { \
return theFunction(__VA_ARGS__); \
} else { \
throw invalid_function_name(FName); \
} \
/**/
注意:通常,名称check / throw也可以合并到假设的get_op_from_name
函数中。这只是一个例子。 VA_ARGS调用可能还有其他通用代码。
一旦我们使用C ++ 11获得可变参数模板,我们就可以使用模板“正确”解决这个问题。
答案 28 :(得分:1)
如果你有一系列用于一堆事物的字段,例如定义一个结构,将该结构序列化为某种二进制格式,进行数据库插入等,然后你可以(递归地!)使用预处理器来避免重复你的字段列表。
这无疑是可怕的。但有时可能比在多个地方更新一长串字段更好?我曾经使用过这种技术一次,而且这一次非常有用。
当然,在具有适当反射的语言中广泛使用相同的一般概念 - 只是对类进行检查并依次对每个字段进行操作。在C预处理器中执行它是脆弱的,难以辨认,并不总是可移植的。所以我有些惶恐地提到它。尽管如此,这是......
(编辑:我现在看到这与@Andrew Johnson在9月18日所说的相似;但是递归地包含相同文件的想法更进一步。)
// file foo.h, defines class Foo and various members on it without ever repeating the
// list of fields.
#if defined( FIELD_LIST )
// here's the actual list of fields in the class. If FIELD_LIST is defined, we're at
// the 3rd level of inclusion and somebody wants to actually use the field list. In order
// to do so, they will have defined the macros STRING and INT before including us.
STRING( fooString )
INT( barInt )
#else // defined( FIELD_LIST )
#if !defined(FOO_H)
#define FOO_H
#define DEFINE_STRUCT
// recursively include this same file to define class Foo
#include "foo.h"
#undef DEFINE_STRUCT
#define DEFINE_CLEAR
// recursively include this same file to define method Foo::clear
#include "foo.h"
#undef DEFINE_CLEAR
// etc ... many more interesting examples like serialization
#else // defined(FOO_H)
// from here on, we know that FOO_H was defined, in other words we're at the second level of
// recursive inclusion, and the file is being used to make some particular
// use of the field list, for example defining the class or a single method of it
#if defined( DEFINE_STRUCT )
#define STRING(a) std::string a;
#define INT(a) long a;
class Foo
{
public:
#define FIELD_LIST
// recursively include the same file (for the third time!) to get fields
// This is going to translate into:
// std::string fooString;
// int barInt;
#include "foo.h"
#endif
void clear();
};
#undef STRING
#undef INT
#endif // defined(DEFINE_STRUCT)
#if defined( DEFINE_ZERO )
#define STRING(a) a = "";
#define INT(a) a = 0;
#define FIELD_LIST
void Foo::clear()
{
// recursively include the same file (for the third time!) to get fields.
// This is going to translate into:
// fooString="";
// barInt=0;
#include "foo.h"
#undef STRING
#undef int
}
#endif // defined( DEFINE_ZERO )
// etc...
#endif // end else clause for defined( FOO_H )
#endif // end else clause for defined( FIELD_LIST )
答案 29 :(得分:1)
您可以使用#define
或-D
选项在编译器命令行上/D
个常量。当为多个平台交叉编译相同的软件时,这通常很有用,因为您可以让makefile控制为每个平台定义的常量。
答案 30 :(得分:1)
我已经使用预处理器从嵌入式系统中使用的浮点值计算定点数,这些浮点值不能在编译代码中使用浮点数。将所有数学运用到真实世界单位并且不必在定点中考虑它们是很方便的。
示例:
// TICKS_PER_UNIT is defined in floating point to allow the conversions to compute during compile-time.
#define TICKS_PER_UNIT 1024.0
// NOTE: The TICKS_PER_x_MS will produce constants in the preprocessor. The (long) cast will
// guarantee there are no floating point values in the embedded code and will produce a warning
// if the constant is larger than the data type being stored to.
// Adding 0.5 sec to the calculation forces rounding instead of truncation.
#define TICKS_PER_1_MS( ms ) (long)( ( ( ms * TICKS_PER_UNIT ) / 1000 ) + 0.5 )
答案 31 :(得分:1)
我认为这个技巧巧妙地使用了无法用函数模拟的预处理器:
#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_ONLY宏。
答案 32 :(得分:0)
宏对于模拟switch语句的语法很有用:
switch(x) {
case val1: do_stuff(); break;
case val2: do_other_stuff();
case val3: yet_more_stuff();
default: something_else();
}
用于非整数值类型。在这个问题中:
Using strings in switch statements - where do we stand with C++17?
您会找到答案,提示一些涉及lambda的方法,但不幸的是,这些宏使我们最接近:
SWITCH(x)
CASE val1 do_stuff(); break;
CASE val2 do_other_stuff();
CASE val3 yet_more_stuff();
DEFAULT something_else();
END
答案 33 :(得分:0)
#define COLUMNS(A,B) [(B) - (A) + 1]
struct
{
char firstName COLUMNS( 1, 30);
char lastName COLUMNS( 31, 60);
char address1 COLUMNS( 61, 90);
char address2 COLUMNS( 91, 120);
char city COLUMNS(121, 150);
};
答案 34 :(得分:0)
您可以在调试版本中启用其他日志记录,并为发布版本禁用它,而无需布尔检查的开销。所以,而不是:
void Log::trace(const char *pszMsg) {
if (!bDebugBuild) {
return;
}
// Do the logging
}
...
log.trace("Inside MyFunction");
你可以:
#ifdef _DEBUG
#define LOG_TRACE log.trace
#else
#define LOG_TRACE void
#endif
...
LOG_TRACE("Inside MyFunction");
如果未定义_DEBUG,则根本不会生成任何代码。您的程序运行得更快,跟踪日志记录的文本将不会编译到您的可执行文件中。
答案 35 :(得分:0)
您可以将其实现为内联函数吗?
#define my_free(x) do { free(x); x = NULL; } while (0)
答案 36 :(得分:0)
在Visual Studio中需要一个用于资源标识符的宏,因为资源编译器只能理解它们(即,它不能用于const或枚举)。
答案 37 :(得分:-1)
我经常会遇到像:
这样的代码int SomeAPICallbackMethod(long a, long b, SomeCrazyClass c, long d, string e, string f, long double yx) { ... }
int AnotherCallback(long a, long b, SomeCrazyClass c, long d, string e, string f, long double yx) { ... }
int YetAnotherCallback(long a, long b, SomeCrazyClass c, long d, string e, string f, long double yx) { ... }
在某些情况下,我会使用以下方法让我的生活更轻松:
#define APIARGS long a, long b, SomeCrazyClass c, long d, string e, string f, long double yx
int SomeAPICallbackMethod(APIARGS) { ... }
它附带了隐藏变量名称的警告,这可能是大型系统中的一个问题,所以这并不总是正确的,只有有时。