什么时候 C + + 宏是有益的?

C + + 社区有理由害怕和回避 C预处理器。内联函数、常量和模板通常比 #define更安全、更优越。

以下宏观:

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

绝不会优于类型安全:

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

但是宏也有它们的位置,请列出在没有预处理器的情况下 不行所使用的宏。

请把每个用例在一个单独的答案,以便它可以投票,如果你知道如何实现没有前处理程序的答案之一,指出如何在该答案的意见。

99681 次浏览

当您想要更改函数中的程序流(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.

通常我最终得到的代码是这样的:

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

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

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

#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

一个常见的用途是检测编译环境,对于跨平台开发,您可以为 Linux 编写一组代码,也可以为 Windows 编写另一组代码,如果没有跨平台库可供您使用的话。

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

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

对于函数,当您想要显式忽略类型安全时,它们非常有用。例如上面和下面的许多用于执行 ASSERT 的示例。当然,像很多 C/C + + 特性一样,你可以搬起石头砸自己的脚,但是语言给了你工具,让你决定做什么。

显而易见的包括警卫

#ifndef MYHEADER_H
#define MYHEADER_H


...


#endif

当你想用一个表达式创建一个字符串时,最好的例子是 assert(#xx的值转换成一个字符串)。

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

方法必须是完整的、可编译的代码; 宏可能是代码片段:

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

然后这样使用它:

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

因为 C + + 11,所以它被 基于范围的循环所取代。

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

马克罗斯永远有他们的位置。

我发现有用的东西是用于调试跟踪的 # Definition DEBUG ——您可以在调试问题时将其保留为1(甚至在整个开发周期中将其保留为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

作为调试函数的包装器,自动传递 __FILE____LINE__等函数:

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

由于 C + + 20的魔法类型 std::source_location可以用来代替 __LINE____FILE__实现一个模拟作为一个正常的函数(模板)。

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

#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

头文件保护需要宏。

有没有其他领域的 必要的宏? 没有很多(如果有的话)。

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

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

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

差不多

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

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

UnitTest + + 这样的 C + + 单元测试框架基本上围绕着预处理器宏。几行单元测试代码扩展成一个类的层次结构,手工输入一点都不好玩。如果没有像 UnitTest + + 这样神奇的预处理器,我不知道如何有效地为 C + + 编写单元测试。

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

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

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

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

我认为这个技巧是对预处理器的一个聪明的使用,它不能用一个函数来模拟:

#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 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 代码的残余部分。

在 VisualStudio 中,资源标识符需要一个宏,因为资源编译器只能理解它们(即,它不能与 const 或 enum 一起工作)。

我偶尔使用宏,以便在一个地方定义信息,但在代码的不同部分以不同的方式使用它。它只是有点邪恶:)

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

您可以使用 # Definition 来帮助调试和单元测试方案。例如,创建内存函数的特殊日志记录变体,并创建一个特殊的 memlog _ preinclude。H:

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

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

gcc -Imemlog_preinclude.h ...

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

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

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

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

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

#define my_free(x) do { free(x); x = NULL; } while (0)

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

我上一份工作是研究病毒扫描器。为了让我的调试更容易,我有很多日志卡在各处,但在一个高需求的应用程序,一个函数调用的费用是太昂贵了。所以,我想出了这个小宏,它仍然允许我在客户站点的发布版本上启用调试日志,而不需要一个函数调用来检查调试标志,只需要返回而不需要记录任何东西,或者如果启用,就会做日志记录... 宏被定义如下:

#define dbgmsg(_FORMAT, ...)  if((debugmsg_flag  & 0x00000001) || (debugmsg_flag & 0x80000000))     { log_dbgmsg(_FORMAT, __VA_ARGS__);  }

因为 log 函数中有 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;
}
...

无论如何,这就是我如何使用它们,我不知道如何帮助模板... 除此之外,我尽量避免使用它们,除非真的有必要。

如果你有一个用于很多事情的字段列表,例如定义一个结构,将该结构序列化为/从一些二进制格式,执行数据库插入等,那么你可以(递归地!)使用预处理器以避免重复您的字段列表。

无可否认,这太可怕了。但是有时候也许比在多个地方更新一个长长的字段列表更好?我只用过一次这种方法,而且那一次非常有用。

当然,同样的一般思想在具有适当反射的语言中得到了广泛的应用——只需反思类并依次操作每个字段即可。在 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 )

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

在编写嵌入式系统程序时,C 是除汇编程序之外的唯一选择。在用 C + + 在桌面上编程,然后切换到更小的嵌入式目标之后,你学会了不再担心那么多简单的 C 功能(包括宏)的“不优雅”,而只是试图从这些功能中找出最佳和安全的用法。

亚历山大 · 斯捷潘诺夫(Alexander Stepanov) :

当我们用 C + + 编程时,我们不应该为它的 C 传统感到羞耻,而应该使用 C + + 的唯一问题,甚至是 C 的唯一问题,出现了 当他们自己不符合自己的逻辑。

主要是:

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

您可以在调试版本中启用额外的日志记录,并在没有布尔检查开销的情况下为发布版本禁用日志记录。所以,不是:

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,则根本不会生成任何代码。您的程序将运行得更快,跟踪日志记录的文本将不会被编译到您的可执行文件中。

字符串常量有时更适合定义为宏,因为与使用 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);

但是,由于 C + + 20,可以实现一个类似字符串的类类型,它可以用作用户定义的字符串文字运算符的非类型模板参数类型,该运算符允许在编译时不使用宏进行连接操作。

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

例子:

同时创建 C 标识符和字符串

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

提高预处理器元编程

我已经使用预处理器从嵌入式系统中使用的不能在编译代码中使用浮点数的浮点值中计算定点数。把你所有的数学知识都放在现实世界的单位中,而不必在定点上考虑它们,这是很方便的。

例如:

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

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

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

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

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

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

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代码时,如果需要可变数量的(泛型)参数,可以使用宏代替模板。

#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获得了可变模板,我们就可以用一个模板“正确地”解决这个问题。

代码重复。

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

宏对于模拟 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