一些有用的宏定义 c++_有用的预处理器宏技巧

一些有用的宏定义 c++

Effective preprocessor meta programming tips

有效的预处理器元编程技巧

C/C++ preprocessor¹ is a tool that is used to process source codes before the compilation process. The preprocessors process certain directives that begin with hash(#) symbol in source codes. There are several preprocessor directives. The most famous one might be #include directive that is used to include a specific file.

C / C ++预处理器¹是一种工具,用于在编译过程之前处理源代码。 预处理器处理某些指令,这些指令以源代码中的hash( # )符号开头。 有几个预处理器指令。 最有名的可能是#include指令,用于包含特定文件。

#include <iostream> // Includes standard library iostream header

Also, there are#if, #ifdef, #ifndef, #elif, #endifdirectives which can be used for conditional compilation² as other examples of preprocessor directives. In this post, I decided to talk about #definedirective and its usage because among all of the directives, certain usages of #define directive have been especially controversial and in some cases, considered to be a bad practice to use it, but in practice, it is still widely used and I like to bring up some case its usage becomes advantageous. For the detail of other types of preprocessor directives, please refer to the documentation³.

另外,还有#if#ifdef#ifndef#elif#endif指令,可用于条件编译#elif ,作为预处理器指令的其他示例。 在本文中,我决定谈论#define指令及其用法,因为在所有指令中,# #define指令的某些用法特别引起争议,在某些情况下,使用它是一种不好的做法,但实际上,它仍然被广泛使用,我想举一些例子,它的使用变得很有优势。 有关其他类型的预处理器指令的详细信息,请参阅文档³。

预处理宏 (Preprocessor Macro)

#definedirective is used to define macro. Macro is a fragment of code that has been given a name. Whenever the macro is used, it is replaced by the contents of the macro. The simple example looks like below.

#define指令用于定义macro 。 宏是已命名的代码片段。 每当使用宏时,它就会被宏的内容替换。 简单的示例如下所示。

#define PI 3.14double radius = 2.0;
double area = PI * radius * radius;

PI is a macro which is categorised as object-like macro. It will be replaced by token 3.14 where it is used in the code by preprocessor.

PI是一个宏,被归类为类对象宏。 它会被令牌3.14替换,在预处理器中代码中会使用它。

The other type of macro is called function-like macro that looks like below

另一种类型的宏称为类似于函数的宏,如下所示

#define max(a, b) ( ((a) > (b)) ? (a) : (b) )
int a = 5;
int b = 4;
int c = max(++a, b); // c becomes 7 not 6!

Actually, this example is often used to explain why function-like macro could bring confusing effects and make the code error prone. In the example above, you would expect the value of a and c to be 6 after the last assignment to c. However, the values are 7 instead of 6. This is because the code after macro expansion looks like below, and as a result

实际上,此示例通常用于解释为什么类函数宏会带来混乱的效果并使代码易于出错。 在上面的示例中,您希望ac的值在最后一次分配给c之后为6 但是,值是7而不是6。这是因为宏扩展后的代码如下所示,因此

int c = ( ((++a) > (b)) ? (++a) : (b) );

the value of a is incremented twice and becomes 7 instead of 6. This can be problematic when users of the macro expect it to work like normal function call.

值被递增两次,并变为7代替6。这可能是有问题当宏的用户期望它像普通的函数调用的工作。

Also there are other reasons that could make macros undesirable: the increased difficulty to debug codes with macros both for human and debuggers, macro not having the concept of scope, it is not type-safe etc.

还有其他一些原因可能会使宏变得不可取:使用人工和调试器使用宏调试代码的难度增加,宏没有作用域的概念,类型不安全等。

However, even though there are some disadvantages of using macros, with careful usages, there are cases where macro can be a helpful tool.

但是,即使使用宏有一些缺点,但如果谨慎使用,在某些情况下宏可能是有用的工具。

为RAII对象创建唯一的名称 (Creating unique names for RAII object)

One example of effective usage of a macro is to create uniquely named variables that are never directly referred to, especially when those variables were used with RAII⁴ technique. At this time, I use an example of ScopeGuard⁵ idiom from Andrei Alexandrescu and Petru Marginean to explain the use case. The idea of ScopeGuard idiom is to let the destructor of guard objects call user specified cleanup actions at the end of scope. Note:I use a very crude implementation of the ScopeGuard idiom to simplify so that we can focus on the macro usage. The implementation looks like below,

有效使用宏的一个示例是创建永远不会直接引用的唯一命名的变量,尤其是当这些变量与RAII®技术一起使用时。 这时,我以Andrei Alexandrescu和Petru Marginean的ScopeGuard惯用法为例来解释用例。 ScopeGuard习惯用法的想法是让保护对象的析构函数在范围的末尾调用用户指定的清除操作。 注意:我使用ScopeGuard惯用语的非常粗糙的实现来简化,以便我们可以专注于宏用法。 实现如下所示,

#define CONCATENATE_IMPL(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __LINE__)
#endifnamespace scope_guard {template <class Func>
class ScopeGuard {
public:
ScopeGuard( Func const& cleanup )
: cleanup_( cleanup ) {}
~ScopeGuard() { cleanup_(); } private:
Func cleanup_;
};enum class ScopeGuardOnExit {};template <typename Fun>
ScopeGuard<Fun> operator+(ScopeGuardOnExit, Fun&& fn) {
return ScopeGuard<Fun>(std::forward<Fun>(fn));
}} // end of namespace scope_guard#define SCOPE_EXIT \
auto ANONYMOUS_VARIABLE(scope_exit_var) \
= ::scope_guard::ScopeGuardOnExit() + [&]()int main() {
std::ofstream myfile;
SCOPE_EXIT{ myfile.close(); std::cout << "Finished cleanup"; };
myfile.open ("example.txt");
myfile << "Writing this to a file.\n";
}

Let’s first look at the usage of the SCOPE_EXITmacro in the main() function. After we created myfileobject, we can declaratively specify the required cleanup steps to be executed when exiting the scope (in this case, out of main function). In this case, it is closing the opened file and outputting the message to indicate the clean-up has been completed. Using this macro, it becomes very clean and intuitive to describe the necessary steps(e.g. releasing resources, logging etc) to be executed when exiting the scope.

我们首先来看一下main()函数中SCOPE_EXIT宏的用法。 创建myfile对象之后,我们可以声明性地指定退出范围时要执行的必需清理步骤(在这种情况下,超出主要功能)。 在这种情况下,它将关闭打开的文件并输出消息以指示清理已完成。 使用此宏,可以很清楚直观地描述退出范围时要执行的必要步骤(例如,释放资源,记录等)。

Now, Let’s see how the SCOPE_EXITmacro is implemented and how it works.

现在,让我们看看SCOPE_EXIT宏是如何实现的以及它是如何工作的。

SCOPE_EXITmacro expands to code like below,

SCOPE_EXIT宏扩展为以下代码,

auto ANONYMOUS_VARIABLE(scope_exit_var) = ::scope_guard::ScopeGuardOnExit() + [&]()

Since SANONYMOUS_VARIABLE(scope_exit_var)is also a macro, it will be expanded too. Let’s look at the corresponding macros defined at the beginning of code.

由于SANONYMOUS_VARIABLE(scope_exit_var)也是一个宏,因此也会对其进行扩展。 让我们看看在代码开头定义的相应宏。

#define CONCATENATE_IMPL(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __LINE__)
#endif

_COUNTER_and_LINE_are special predefined macros. In this example, they are used to provide unique numbers on each place where it is expanded in the code. From Gcc release notes⁶:

_COUNTER__LINE_是特殊的预定义宏。 在此示例中,它们用于在代码中对其进行扩展的每个位置提供唯一编号。 从Gcc发行说明⁶:

A new predefined macro __COUNTER__ has been added. It expands to sequential integral values starting from 0. In conjunction with the ## operator, this provides a convenient means to generate unique identifiers.

添加了新的预定义宏__COUNTER__ 。 它扩展为从0开始的顺序整数值。结合##运算符,这提供了一种生成唯一标识符的便捷方法。

But not all compilers support _COUNTER_so, it uses standard predefined_LINE_ macro as a backup. This macro expands to the current input line number, in the form of a decimal integer constant.

但是并非所有编译器都支持_COUNTER_因此它使用标准的预定义_LINE_宏作为备份。 该宏以十进制整数常量的形式扩展为当前输入行号。

CONCATENATEmacro is defined to concatenate given two inputs. To make it work with the predefined macros, it needs two level of macro expanding. This is why we also have CONCATENATE_IMPLmacro. CONCATENATE_IMPLmacro uses ##macro operator that takes two separate tokens and pastes them together to form a single token. The resulting token could be a variable name, class name or any other identifier.

CONCATENATE宏被定义为连接给定的两个输入。 要使其与预定义的宏一起使用,它需要两个级别的宏扩展。 这就是为什么我们还有CONCATENATE_IMPL宏的原因。 CONCATENATE_IMPL宏使用##宏运算符,该运算符采用两个单独的标记并将它们粘贴在一起以形成单个标记。 结果令牌可以是变量名,类名或任何其他标识符。

As a result, the expansion of ANONYMOUS_VARIABLE(scope_exit_var)can be like scope_exit_var1(The number at the end depends on where it is called in source code_LINE_, how many times called _COUNTER_).

结果, ANONYMOUS_VARIABLE(scope_exit_var)的扩展可以像scope_exit_var1一样(末尾的数字取决于在源代码_LINE_被调用的_LINE_ ,多少次被称为_COUNTER_ )。

Hence, the call to SCOPE_EXITmacro in the example is expanded like below and now, we are only left with the normal C++ code.

因此,示例中对SCOPE_EXIT宏的调用像下面这样展开,现在,我们只剩下普通的C ++代码了。

auto scope_exit_var1 = ::scope_guard::ScopeGuardOnExit() + [&]() { myfile.close(); std::cout << "Finished cleanup"; };

Now, let’s look at the original l example code again. It defines a templates operator function that takes enum class ScopeGuardOnExitand a template argument Fun&& fnas arguments.

现在,让我们再次看一下原始的示例代码。 它定义了一个模板运算符函数,该函数以枚举类ScopeGuardOnExit和模板参数Fun&& fn作为参数。

template <typename Fun>
ScopeGuard<Fun> operator+(ScopeGuardOnExit, Fun&& fn) {
return ScopeGuard<Fun>(std::forward<Fun>(fn));
}

This is the operator called on the right hand side of the expanded code.

这是在扩展代码右侧调用的运算符。

It forwards the templates argument funto the constructor ofScopeGuardclass and return the constructed object. In this case, the templates argument funis lambda function⁷

它将模板参数fun转发给ScopeGuard类的构造函数,并返回构造的对象。 在这种情况下,模板参数fun是lambda函数⁷

[&]() { myfile.close(); std::cout << "Finished cleanup"; };

ScopeGuard is a class that takes a templates parameter Func. It has a cleanup_member of Functype which is initialised with a given argument in the constructor. The cleanup_is called in the destructor of ScopeGuard, in this case, it is the given lambda function. As a result, the lambda function which contains the user specified cleanup steps is executed when the scope_exit_var1variable goes out of scope and the destructor of the object is destructed!

ScopeGuard是一个带有模板参数Func 。 它具有Func类型的cleanup_成员,该成员在构造函数中使用给定参数初始化。 在ScopeGuard的析构函数中调用cleanup_ ,在这种情况下,它是给定的lambda函数。 结果,当scope_exit_var1变量超出范围并且对象的析构函数被破坏时,将执行包含用户指定的清理步骤的lambda函数!

As I mentioned above, I limited the implementation of the ScopeGuard to minimum to make it simpler to explain the idea of the macro usage. In practice, for example, we need to make it uncopyable, and also it can be extended like ScopeGuardOnFailure to specify the steps to be executed on exceptions etc as you can see from the talk “Declarative Control Flow”⁸ by Andrei Alexandrescu.

如前所述,我将ScopeGuard的实现限制在最低限度,以使其更易于解释宏用法的概念。 例如,实际上,我们需要使其不可复制,并且还可以像ScopeGuardOnFailure一样对其进行扩展,以指定要对异常执行的步骤,例如您可以从Andrei Alexandrescu的“声明性控制流”⁸中看到的那样。

摘要 (Summary)

In this post, I explained the basic of preprocessor directives and macro. Then I talked about the disadvantages of using macro in codes and why there are controversies on the usage among communities. Even with the potential issues with macro, it is still widely used in practice and it can be useful if we use it carefully, as an example, I introduced the technique of creating unique variable name using macro with an example of ScopeGuard idiom use case.

在这篇文章中,我解释了预处理器指令和宏的基础。 然后,我谈到了在代码中使用宏的弊端,以及为什么在社区之间的使用方面存在争议。 即使存在宏的潜在问题,它仍然在实践中得到广泛使用,并且如果我们仔细使用它会很有用,例如,我以ScopeGuard习惯用例为例,介绍了使用宏创建唯一变量名的技术。

[1]: https://en.wikipedia.org/wiki/C_preprocessor

[1]: https//en.wikipedia.org/wiki/C_preprocessor

[2]:https://en.wikipedia.org/wiki/Conditional_compilation#:~:text=In%20computer%20programming%2C%20conditional%20compilation,that%20are%20provided%20during%20compilation.

[2]: https : //zh.wikipedia.org/wiki/Conditional_compilation# :~: text=In%20computer%20programming%2C%20conditional%20compilation,%20是%20在%20编译期间提供的。

[3]: https://en.cppreference.com/w/cpp/preprocessor

[3]: https//en.cppreference.com/w/cpp/preprocessor

[4]:https://en.cppreference.com/w/cpp/language/raii

[4]: https//en.cppreference.com/w/cpp/language/raii

[5]:https://www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758

[5]: https//www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758

[6]:https://gcc.gnu.org/gcc-4.3/changes.html

[6]: https//gcc.gnu.org/gcc-4.3/changes.html

[7]:https://en.cppreference.com/w/cpp/language/lambda

[7]: https//en.cppreference.com/w/cpp/language/lambda

[8]:https://youtu.be/WjTrfoiB0MQ

[8]: https//youtu.be/WjTrfoiB0MQ

翻译自: https://levelup.gitconnected.com/useful-preprocessor-macro-trick-1f91f526a80f

一些有用的宏定义 c++

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值