182

C预处理器有理由被 C++ 社区恐惧和回避。内联函数、常量和模板通常是比#define.

以下宏:

#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)  

绝不优于安全类型:

inline bool succeeded(int hr) { return hr >= 0; }

但是宏确实有它们的位置,请列出您发现的宏的用途,如果没有预处理器就无法做到。

请将每个用例放在一个单独的答案中,以便对其进行投票,并且如果您知道如何在没有预处理者的情况下获得其中一个答案,请在该答案的评论中指出如何。

4

38 回答 38

129

作为调试函数的包装器,自动传递__FILE__,__LINE__等内容:

#ifdef ( DEBUG )
#define M_DebugLog( msg )  std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif
于 2008-09-18T19:56:26.227 回答
94

方法必须始终是完整的、可编译的代码;宏可能是代码片段。因此,您可以定义一个 foreach 宏:

#define foreach(list, index) for(index = 0; index < list.size(); index++)

并这样使用它:

foreach(cookies, i)
    printf("Cookie: %s", cookies[i]);

从 C++11 开始,这被基于范围的 for 循环所取代。

于 2008-09-18T19:54:22.240 回答
62

头文件保护需要宏。

还有其他需要宏的领域吗?不多(如果有的话)。

还有其他可以从宏中受益的情况吗?是的!!!

我使用宏的一个地方是非常重复的代码。例如,当包装 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__预处理器宏。

无论如何,正确使用宏是非常有用的。宏并不邪恶——滥用它们是邪恶的。

于 2008-09-18T20:02:33.983 回答
54

大多:

  1. 包括警卫
  2. 条件编译
  3. 报告(预定义的宏,如__LINE____FILE__
  4. (很少)复制重复的代码模式。
  5. 在竞争对手的代码中。
于 2008-10-17T16:03:59.657 回答
50

在条件编译内部,克服编译器之间的差异问题:

#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
于 2008-09-18T20:00:32.460 回答
37

当您想从表达式中创建一个字符串时,最好的例子是assert#x将值x转换为字符串)。

#define ASSERT_THROW(condition) \
if (!(condition)) \
     throw std::exception(#condition " is false");
于 2008-09-18T19:54:05.797 回答
34

有时将字符串常量更好地定义为宏,因为您可以使用字符串文字做更多的事情而不是使用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);
于 2008-10-22T20:26:21.997 回答
24

当您想要更改程序流(和)时return,函数中的代码的行为与函数中实际内联的代码不同。breakcontinue

#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.
于 2008-09-18T19:50:47.303 回答
20

明显的包括警卫

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif
于 2008-09-18T19:53:01.773 回答
17

像UnitTest++这样的 C++ 单元测试框架几乎都围绕预处理器宏展开。几行单元测试代码扩展成一个层次结构的类,手动输入一点也不好玩。如果没有 UnitTest++ 之类的东西和它的预处理器魔法,我不知道您将如何有效地为 C++ 编写单元测试。

于 2008-09-18T20:04:27.597 回答
17

假设我们将忽略诸如头卫之类的明显事物。

有时,您想要生成需要由预编译器复制/粘贴的代码:

#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;
}

(注:代码复制/粘贴自 boost 主页)

哪个(恕我直言)比std::for_each.

因此,宏总是有用的,因为它们超出了正常的编译器规则。但我发现大多数时候我看到一个,它们实际上是 C 代码的剩余部分,从未翻译成正确的 C++。

于 2008-09-18T20:13:37.607 回答
16

您不能使用常规函数调用来执行函数调用参数的短路。例如:

#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
于 2008-09-18T19:52:39.700 回答
14

害怕 C 预处理器就像害怕白炽灯泡一样,仅仅因为我们有荧光灯泡。是的,前者可以是{电| 程序员时间}效率低下。是的,你可以(字面上)被他们烧死。但是,如果您处理得当,他们可以完成工作。

当您对嵌入式系统进行编程时,C 曾经是除了汇编程序之外的唯一选择。在使用 C++ 在桌面上进行编程,然后切换到更小的嵌入式目标之后,您将学会不再担心这么多裸 C 功能(包括宏)的“不优雅”,而只是试图找出您可以从中获得的最佳和安全用法特征。

亚历山大·斯捷潘诺夫

当我们用 C++ 编程时,我们不应该为它的 C 遗产感到羞耻,而是要充分利用它。C++ 的唯一问题,甚至 C 的唯一问题,都是在它们本身与自己的逻辑不一致时出现的。

于 2008-10-17T15:46:44.823 回答
10

仍然可以使用预处理器(宏)构建一些非常先进和有用的东西,而使用包括模板在内的 c++“语言结构”是永远无法做到的。

例子:

使某物既是 C 标识符又是字符串

在 C 中使用枚举类型的变量作为字符串的简单方法

Boost 预处理器元编程

于 2008-10-23T21:49:30.173 回答
9

我们在信息丰富的异常抛出、捕获和日志记录中使用__FILE____LINE__宏进行诊断,同时在我们的 QA 基础架构中使用自动日志文件扫描器。

例如,抛出宏OUR_OWN_THROW可以与异常类型和该异常的构造函数参数一起使用,包括文本描述。像这样:

OUR_OWN_THROW(InvalidOperationException, (L"Uninitialized foo!"));

这个宏当然会抛出InvalidOperationException异常,并将描述作为构造函数参数,但它也会向日志文件写入一条消息,该日志文件由发生抛出的文件名和行号及其文本描述组成。抛出的异常将获得一个 id,该 id 也会被记录。如果异常在代码中的其他地方被捕获,它将被标记为这样,然后日志文件将指示该特定异常已被处理,因此它不太可能导致稍后可能记录的任何崩溃。我们的自动化 QA 基础设施可以轻松发现未处理的异常。

于 2008-09-18T20:05:51.697 回答
8

代码重复。

看看boost preprocessor library,它是一种元元编程。在 topic->motivation 中你可以找到一个很好的例子。

于 2011-11-06T12:41:18.530 回答
7

一个常见的用途是检测编译环境,对于跨平台开发,您可以为 linux 编写一组代码,例如,当没有跨平台库供您使用时,您可以为 windows 编写另一组代码。

因此,在一个粗略的示例中,跨平台互斥锁可以具有

void lock()
{
    #ifdef WIN32
    EnterCriticalSection(...)
    #endif
    #ifdef POSIX
    pthread_mutex_lock(...)
    #endif
}

对于函数,当您想显式忽略类型安全时,它们很有用。比如上面和下面的很多例子都是做 ASSERT 的。当然,就像许多 C/C++ 功能一样,您可以自责,但该语言为您提供了工具并让您决定要做什么。

于 2008-09-18T19:52:45.630 回答
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;

在私有初始化函数中,所有字段都可用于使用数据填充表:

#define FIELD(name, desc, value) \
    table[FIELD_ ## name].desc = desc; \
    table[FIELD_ ## name].value = value;

#include "field_list.h"
于 2008-09-18T20:50:04.803 回答
6

就像是

void debugAssert(bool val, const char* file, int lineNumber);
#define assert(x) debugAssert(x,__FILE__,__LINE__);

这样您就可以例如拥有

assert(n == true);

如果 n 为假,则将问题的源文件名和行号打印到您的日志中。

如果您使用正常的函数调用,例如

void assert(bool val);

而不是宏,你所能得到的只是你的断言函数的行号打印到日志中,这将不太有用。

于 2008-09-18T20:03:36.633 回答
4
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

与当前线程中讨论的“首选”模板解决方案不同,您可以将其用作常量表达式:

char src[23];
int dest[ARRAY_SIZE(src)];
于 2008-09-18T21:41:01.093 回答
3

当您在编译时就编译器/操作系统/硬件特定行为做出决定时。

它允许您创建与编译器/操作系统/硬件特定功能的接口。

#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
于 2008-09-18T19:55:04.920 回答
3

您可以使用#defines 来帮助调试和单元测试方案。例如,创建内存函数的特殊日志变体并创建一个特殊的 memlog_preinclude.h:

#define malloc memlog_malloc
#define calloc memlog calloc
#define free memlog_free

使用以下代码编译您的代码:

gcc -Imemlog_preinclude.h ...

memlog.o 中指向最终图像的链接。您现在控制 malloc 等,可能是为了记录日志,或者模拟单元测试的分配失败。

于 2008-09-18T21:03:34.973 回答
2

编译器可以拒绝您的内联请求。

宏将永远占有一席之地。

我发现#define DEBUG 用于调试跟踪很有用——您可以在调试问题时将其保留为 1(甚至在整个开发周期中将其保持打开状态),然后在交付时将其关闭。

于 2008-09-18T19:54:39.617 回答
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;
   }
...

无论如何,这就是我使用它们的方式,而且我不确定如何使用模板来帮助...除此之外,我尽量避免使用它们,除非真的有必要。

于 2008-09-19T03:52:38.610 回答
2

我使用宏来轻松定义异常:

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;\
    };\
  }\
于 2011-06-15T21:13:21.000 回答
1

您可以使用or选项#define在编译器命令行上设置常量。这在为多个平台交叉编译相同的软件时通常很有用,因为您可以让您的 makefile 控制为每个平台定义的常量。-D/D

于 2008-09-19T03:44:59.813 回答
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 )
于 2008-10-03T02:16:15.507 回答
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 )
于 2009-08-18T23:46:43.227 回答
1

又一个 foreach 宏。T:类型,c:容器,i:迭代器

#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;
}

提供更好的实现:谷歌“BOOST_FOREACH”

可用的好文章:有条件的爱:FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html

于 2010-02-04T11:33:17.190 回答
1

也许宏的最大用途是在独立于平台的开发中。考虑类型不一致的情况——使用宏,你可以简单地使用不同的头文件——比如:--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

在我看来,比以其他方式实现它更具可读性。

于 2010-04-18T12:37:15.150 回答
1

到目前为止,似乎 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);                  \
  }                                                      \
/**/

注意:通常,名称检查/抛出也可以合并到假设get_op_from_name函数中。这只是一个例子。围绕 VA_ARGS 调用可能还有其他通用代码。

一旦我们使用 C++11 获得可变参数模板,我们就可以使用模板“正确”地解决这个问题。

于 2011-08-16T12:03:32.367 回答
0

很多时候我最终得到如下代码:

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) { ... } 

它带有真正隐藏变量名称的警告,这在较大的系统中可能是一个问题,所以这并不总是正确的做法,只是有时

于 2008-09-18T19:52:34.100 回答
0

我认为这个技巧是对无法用函数模拟的预处理器的巧妙使用:

#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 宏。

于 2008-09-18T20:06:30.667 回答
0

您需要 Visual Studio 中资源标识符的宏,因为资源编译器只能理解它们(即,它不适用于 const 或 enum)。

于 2008-09-18T20:17:08.363 回答
0

您可以将其实现为内联函数吗?

#define my_free(x) do { free(x); x = NULL; } while (0)
于 2008-09-19T02:31:09.150 回答
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 未定义时,这根本不会生成任何代码。您的程序将运行得更快,并且跟踪记录的文本不会编译到您的可执行文件中。

于 2008-10-17T16:08:53.890 回答
-1
#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);
};
于 2011-05-08T04:12:25.953 回答
-1

宏对于模拟 switch 语句的语法很有用:

switch(x) {
case val1: do_stuff(); break;
case val2: do_other_stuff();
case val3: yet_more_stuff();
default:   something_else();
}

对于非整数值类型。在这个问题中:

在 switch 语句中使用字符串——我们在 C++17 中的立场是什么?

你会发现答案暗示了一些涉及 lambdas 的方法,但不幸的是,它是让我们最接近的宏:

SWITCH(x)
CASE val1  do_stuff(); break;
CASE val2  do_other_stuff();
CASE val3  yet_more_stuff();
DEFAULT    something_else();
END
于 2018-08-03T14:24:59.233 回答