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

以下宏:

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

绝不优于类型安全:

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

但宏确实有其用武之地,请列出您发现的宏的用途 不能 无需预处理器即可完成。

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

有帮助吗?

解决方案

作为调试函数的包装器,自动传递诸如 __FILE__, __LINE__, , ETC:

#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__ 预处理器宏。

不管怎样,如果使用得当,宏是非常有用的。宏并不是邪恶的——它们 滥用 是邪恶的。

大多:

  1. 包括警卫
  2. 条件编译
  3. 报告(预定义宏,例如 __LINE____FILE__)
  4. (很少)复制重复的代码模式。
  5. 在你竞争对手的代码中。

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

#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

当您想从表达式中生成字符串时,最好的例子是 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);

当你想改变程序流程时(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.

明显包括警卫

#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

假设我们将忽略诸如标头防护之类的明显内容。

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

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

C++ 的单元测试框架,例如 单元测试++ 几乎都是围绕预处理器宏进行的。几行单元测试代码扩展为类的层次结构,手动键入根本没有乐趣。如果没有像 UnitTest++ 这样的东西和它的预处理器魔力,我不知道如何有效地为 C++ 编写单元测试。

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

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

亚历山大·斯捷潘诺夫 :

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

我们使用 __FILE____LINE__ 用于诊断目的的宏,信息丰富的异常抛出、捕获和日志记录,以及我们 QA 基础设施中的自动日志文件扫描器。

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

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

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

代码重复。

看看 boost预处理器库, ,这是一种元元编程。在主题->动机中你可以找到一个很好的例子。

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

例子:

使某些东西既是 C 标识符又是字符串

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

Boost 预处理器元编程

我偶尔会使用宏,这样我就可以在一处定义信息,但在代码的不同部分以不同的方式使用它。这只是有点邪恶:)

例如,在“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"

一个常见的用途是检测编译环境,对于跨平台开发,您可以为 Linux 编写一组代码,如果不存在适合您的目的的跨平台库,则可以为 Windows 编写另一组代码。

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

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

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

就像是

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);

除了宏之外,您所能得到的只是打印到日志中的断言函数的行号,这不太有用。

#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

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

char src[23];
int dest[ARRAY_SIZE(src)];

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

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

使用以下命令编译代码:

gcc -Imemlog_preinclude.h ...

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

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

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

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

宏将永远占有一席之地。

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

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

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

#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

在我的上一份工作中,我负责病毒扫描程序。为了让我更容易调试,我在各处都贴了很多日志记录,但在像这样的高需求应用程序中,函数调用的费用太昂贵了。因此,我想出了这个小宏,它仍然允许我在客户站点的发布版本上启用调试日志记录,而无需函数调用的成本,该函数调用将检查调试标志并仅返回而不记录任何内容,或者如果启用的话,会进行日志记录...该宏定义如下:

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

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

另一个 foreach 宏。电话:类型,c:容器,我:迭代器

#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 回归 (埃里克·尼伯勒) http://www.artima.com/cppsource/foreach.html

也许宏的最大用途是在独立于平台的开发中。考虑类型不一致的情况 - 使用宏,您可以简单地使用不同的头文件 - 例如:--WIN_TYPES.H

typedef ...some struct

--POSIX_TYPES.h

typedef ...some another struct

--程序.h

#ifdef WIN32
#define TYPES_H "WINTYPES.H"
#else 
#define TYPES_H "POSIX_TYPES.H"
#endif

#include TYPES_H

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

似乎到目前为止 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 获得了可变参数模板,我们就可以使用模板“正确”地解决这个问题。

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top