C ++宏什么时候有益?[关闭]


176

ç预处理器有理由担心和C ++社区避之唯恐不及。内联函数,const和模板通常是的更安全和优越的替代方法#define

以下宏:

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

绝不优于安全类型:

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

但是宏确实有其位置,请列出您在没有预处理器的情况下无法发现的宏用途。

请把每个用例放在一个单独的答案中,以便可以将其投票。如果您知道如何在没有预告的情况下获得答案之一,请指出该答案的注释方式。


我曾经使用一个充满宏的C ++应用程序,该过程花费了45分钟的构建时间,用内联函数替换了这些宏,并且将构建时间缩短到了不到15分钟。
endian


线程与宏有利的上下文有关,而不是与宏无关的上下文。
underscore_d

Answers:


123

作为包装的调试功能,以自动传递之类的东西__FILE____LINE__等:

#ifdef ( DEBUG )
#define M_DebugLog( msg )  std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif

14
实际上,原始代码段:<< FILE “:” <<很好,FILE会生成一个字符串常量,预处理器会将其与“:”连接为单个字符串。
Frank Szczerba 09年

12
这仅需要预处理器,因为__FILE__并且__LINE__ 需要预处理器。在代码中使用它们就像预处理器的感染载体。
TED 2012年

93

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

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

并按以下方式使用它:

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

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


6
+1如果您使用一些非常复杂的迭代器语法,编写一个foreach样式宏可以使您的代码更易于阅读和维护。我已经做到了,它有效。
未来主义者

9
大多数注释与宏可能是代码片段而不是完整代码这一点完全无关。但是,谢谢您的挑剔。
jdmichal

12
这不是C ++。如果您使用的是C ++,则应使用迭代器和std :: for_each。
2009年

20
基督,我不同意。在lambda之前,这for_each是一件令人讨厌的事情,因为每个元素通过runnnig的代码都不在调用点本地。 foreach,(并且我强烈建议您BOOST_FOREACH使用手动解决方案,而不是手动解决方案),让您将代码保持在迭代站点附近,以使其更具可读性。就是说,一旦lambda推出,for_each可能又会成为路途。
GManNickG

8
值得注意的是,BOOST_FOREACH本身就是一个宏(但它是一个经过深思熟虑的宏)
Tyler McHenry

59

头文件防护需要宏。

还有其他需要宏的区域吗?没有很多(如果有)。

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

我使用宏的一个地方是重复性很强的代码。例如,当包装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__宏。

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


7
#pragma once如今大多数编译器都支持,所以我怀疑确实需要警卫
1800信息

13
如果您是为所有编译器而不是仅为大多数编译器编写,它们就是;-)
Steve Jessop

30
因此,建议您使用预处理程序扩展而不是可移植的标准预处理程序功能,以避免使用预处理程序?在我看来有点荒谬。
Logan Capaldo

#pragma once在许多常见的构建系统上中断。
Miles Rout 2014年

4
有一个不需要宏的解决方案:void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }。在功能方面:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
MikeMB

51

大多:

  1. 包括警卫
  2. 条件编译
  3. 报告(如__LINE__和的预定义宏__FILE__
  4. (很少)复制重复的代码模式。
  5. 用您竞争对手的代码。

寻找有关如何实现数字5的帮助。您能引导我寻求解决方案吗?
最多

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

12
在C ++中,可以通过使用内联函数获得相同的结果:<code> #ifdef ARE_WE_ON_WIN32 <br> inline int close(int i){return _close(i); } <br> #endif </ code>
paercebal

2
这将删除#define的,但不会删除#ifdef和#endif。无论如何,我同意你的看法。
Gorpik

19
永远不要定义小写的宏。更改功能的宏是我的噩梦(感谢Microsoft)。最好的例子是第一行。许多库都有close函数或方法。然后,当您将此库的标头和此宏的标头包含在内时,如果您遇到了很大的问题,则将无法使用库API。
Marek R

AndrewStein,您认为在这种情况下使用宏比@paercebal的建议有什么好处吗?如果没有,似乎宏实际上是免费的。
einpoklum '16

1
#ifdef WE_ARE_ON_WIN32plz :)
轻轨赛将于

38

当您想用表达式制作字符串时,最好的例子是assert#x将value x转换为字符串)。

#define ASSERT_THROW(condition) \
if (!(condition)) \
     throw std::exception(#condition " is false");

5
只是一个小问题,但我个人会保留分号。
迈克尔·迈尔斯

10
我同意,实际上我会将其放在do {} while(false)中(以防止发生其他劫机行为),但我想保持其简单性。
Motti

33

有时最好将字符串常量定义为宏,因为使用字符串常量比使用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);

2
在C ++ 11中,我认为这是最重要的部分(除了包括保护)。宏确实是我们在编译时字符串处理中拥有的最好的东西。我希望这是C ++ 11 ++的功能
David Stone

1
这种情况导致我希望使用C#中的宏。
罗林

2
我希望我可以+42。字符串文字的一个非常重要的,尽管不经常被记住的方面。
Daniel Kamil Kozar

24

当你想改变程序流(returnbreakcontinue在一个函数的行为)代码不同于其在功能其实内联代码。

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

在我看来,抛出异常是一个更好的选择。
einpoklum '16

编写python C(++)扩展时,通过设置异常字符串,然后返回-1或来传播异常NULL。因此,宏可以大大减少那里的样板代码。
black_puppydog16年

20

显然包括后卫

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif

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

3
也许是更笼统的观点:函数只对它们的参数进行一次评估。宏可以多次或多次计算参数。
史蒂夫·杰索普

@ [Greg Rogers]宏预处理器所做的所有事情都是替换文本。一旦您了解了这一点,就不会再有任何神秘之处了。
1800信息

您可以通过对andf进行模板化来获得等效的行为,而不是在调用函数之前将求值强制为bool。如果不亲自尝试,我不会意识到您说的是真的。有趣。
格雷格·罗杰斯

您究竟如何使用模板来做到这一点?
1800信息

6
将短路操作隐藏在函数样式宏的后面是我真的不想在生产代码中看到的东西之一。
MikeMB'7

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主页复制/粘贴)

(IMHO)哪种方法比更好std::for_each

因此,宏总是有用的,因为它们不在正常的编译器规则之内。但是我发现,大多数时候我看到的是,它们实际上是C代码的剩余部分,从未被翻译成适当的C ++。


1
仅将CPP用于编译器无法执行的操作。例如,RAISE_ERROR_STL应该仅使用CPP来确定文件,行和函数签名,并将它们传递给执行其余操作的函数(可能是行内)。
Rainer Blome

请更新您的答案以反映C ++ 11并解决@RainerBlome的评论。
einpoklum '16

@RainerBlome:我们同意。RAISE_ERROR_STL宏是C ++ 11之前的版本,因此在这种情况下,它是完全合理的。我的理解(但是我从来没有机会处理那些特定功能)是,您可以在Modern C ++中使用可变参数模板(或宏?)来更优雅地解决问题。
paercebal '16

@einpoklum:“请更新您的答案以反映C ++ 11并解决RainerBlome的评论”。。。我相信,充其量,我将添加一个有关Modern C ++的部分,其中的替代实现将减少或消除对宏的需求,但重点是:宏是丑陋且邪恶的,但是当您需要执行某些操作时,编译器将无法理解,您可以通过宏进行操作。
paercebal '16

即使使用C ++ 11,宏也可以为函数做很多事情:#include <sstream> #include <iostream> using namespace std; void trace(char const * file, int line, ostream & o) { cerr<<file<<":"<<line<<": "<< static_cast<ostringstream & >(o).str().c_str()<<endl; } struct Oss { ostringstream s; ostringstream & lval() { return s; } }; #define TRACE(ostreamstuff) trace(__FILE__, __LINE__, Oss().lval()<<ostreamstuff) int main() { TRACE("Hello " << 123); return 0; }这样,宏就更短了。
Rainer Blome '16

16

C ++的单元测试框架(如UnitTest ++)几乎都围绕预处理程序宏。几行单元测试代码扩展到一个类的层次结构中,手动键入根本不会很有趣。没有像UnitTest ++这样的东西,它没有预处理器的魔力,我不知道您如何有效地为C ++编写单元测试。


无需框架即可编写单元测试。最后,它实际上仅取决于您想要哪种输出。如果您不在乎,则指示成功或失败的简单退出值应该很好。
清晰的

15

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

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

亚历山大·斯蒂芬诺夫 Alexander Stepanov)

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


我认为这是错误的态度。仅仅因为您可以学会“正确处理”,并不意味着这值得任何人的时间和精力。
尼尔·G

9

我们将__FILE____LINE__宏用于诊断目的,以进行信息丰富的异常抛出,捕获和日志记录,以及我们质量检查基础架构中的自动日志文件扫描程序。

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

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

当然,该宏将引发InvalidOperationException异常,并将描述作为构造函数参数,但是它还将向日志文件中写入一条消息,该日志文件由发生异常的文件名和行号及其文本描述组成。引发的异常将获得一个ID,该ID也将被记录。如果该异常曾在代码中的其他位置捕获,则将其标记为此类,然后日志文件将指示该特定异常已得到处理,因此,它不太可能导致以后记录的崩溃。我们的自动化质量检查基础架构可以轻松处理未处理的异常。




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"

1
注意:即使没有单独的包含,也可以实现类似的技术。请参阅: stackoverflow.com/questions/147267/... stackoverflow.com/questions/126277/...
苏马

6

一种常见的用途是检测编译环境,例如,对于跨平台开发,您可以编写一套针对linux的代码,而另一套针对Windows的代码,前提是没有跨平台的库可用于您的目的。

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

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

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


既然提问者问:通过使用每个平台的不同包含路径包含不同的标头,就可以在没有宏的情况下完成此操作。我倾向于同意,尽管宏通常更方便。
史蒂夫·杰索普

我同意那个。如果您开始为此目的使用宏,则代码很快就会变得可读性差
Nemanja Trifunovic

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

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


为什么要重新发明轮子,当标准库的实现已经通过提供<cassert>assert()宏,它转储文件/行/功能信息?(无论如何,在我见过的所有实现中)
underscore_d

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

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

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

2
这可以用一个更安全的方式模板来完成(如果传递了一个指针,而不是数组,它不会编译)stackoverflow.com/questions/720077/calculating-size-of-an-array/...
Motti

1
既然我们在C ++ 11中有了constexpr,那么安全(非宏)版本也可以在常量表达式中使用。template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
David Stone

3

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

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

使用以下代码编译代码:

gcc -Imemlog_preinclude.h ...

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


3

当您在编译时通过特定于Compiler / OS /硬件的行为进行决策时。

它允许您将界面设置为Comppiler / OS /硬件特定功能。

#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

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

2

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

宏将始终占有一席之地。

我发现有用的是#define DEBUG以进行调试跟踪-您可以在调试问题时将其保留为1(甚至在整个开发周期中将其保留),然后在交付时将其关闭。


10
如果编译器拒绝您的内联请求,则可能有很好的理由。好的编译器比正确的嵌入式处理要好得多,而差的编译器将给您带来更多的性能问题。
David Thornley

@DavidThornley或者它可能不是一个很棒的优化编译器,例如GCC或CLANG / LLVM。一些编译器只是胡扯。
Miles Rout 2014年

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

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


2

还有另一个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;
}

更好的实现方式:Google “ BOOST_FOREACH”

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


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

我认为这比以其他方式实现它更具可读性。


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);                  \
  }                                                      \
/**/

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

一旦使用C ++ 11获得了可变参数模板,就可以使用模板“适当地”解决此问题。


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


2
根据标准,此技巧无效。它试图通过预处理器创建注释标记,但是注释将在预处理器运行之前被删除。合格的编译器将在此处导致语法错误。
David Thornley,

2
对不起,大卫,但编译器必须包含注释删除的第二个副本。
约书亚

使调试标志成为全局const bool更加容易,并使用如下代码:if(debug)cout <<“ ...”; -不需要宏!
Stefan Monov 2010年

@Stefan:的确如此,这就是我现在要做的。在这种情况下,如果debug为false,则任何体面的编译器均不会生成任何代码。
MathieuPagé2010年

1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.