C++系列学习——Macro Free
Posted 码著
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++系列学习——Macro Free相关的知识,希望对你有一定的参考价值。
C++系列学习--Macro Free
One of C++'s aims is to make C's preprocessor redundant because I consider its actions inherently error prone.
——Bjarne Stroustrup
背景
C预处理器本质是一个文本替换工具,用来在实际编译之前进行一定的预处理操作,一般情况下#开头的预处理操作并不认为是语言本身的一部分,因为编译器永远看不到这些宏定义符号。
以C++来说,用宏的目的并不是出于性能的缘由,更多的只是为了减少重复的代码和进行条件编译。随着modern cpp的发展,越来越的新特性加入使得对宏的使用依赖进一步降低。本文将关注如何使用C++新特性替换C预处理程序。
如何替代宏的使用
表达式别名
有一些宏定义会用在表达式别名,替换后的文本会被识别为C++表达式,对于这种情况比较简单的是使用常量表达式或者lambda替换宏,
void summer()
{
int a = 1, b=2;
int c = SUM;
}
// ========================>>>
constexpr auto PI = 3.14;
constexpr auto SEVEN = 3 + 4;
constexpr auto FILENAME = "header.h";
void summer()
{
int a = 1, b=2;
auto SUM = [&a, &b]() { return a + b; };
int c = SUM();
}
类型别名
类型别名是一个类似于对象的宏,其替换文本可以识别为C++类型表达式。对于这种,可以使用C++的别名声明来替换:
// ========================>>>
using A = T;
参数表达式
参数表达式是一种类似于函数的宏,替换文本后会扩展为表达式或语句。对于这种使用,C++中的最佳实践是使用内联模版函数。
#define ASSIGN(A, B) { B = A; }
// ========================>>>
template <typename T1, typename T2>
inline auto MIN(T1&& A, T2&& B)
-> decltype(((A) < (B) ? (A) : (B)))
{
return ((A) < (B) ? (A) : (B));
}
template <typename T1, typename T2>
inline void ASSIGN(T1&& A, T2&& B) {
B = A;
}
这里引入了内联,自动的推导类型和完美转发等modern c++的特性。完美转发使得调用方可以根据需要决定参数传递的类型。
参数化类型别名
这种其实就是模版别名,在C++11之前需要用宏去实现。
// ========================>>>
template <typename T>
using AliasMap = std::map<std::string, T>;
条件编译
目前绝大多数开源的C++项目都会依赖宏来进行条件编译,其本质意义是通过定义宏与否来改变某个定义/声明。
比如存在一个绘制三角形的API,但其具体实现会根据操作系统而变化,通过预处理器就可以很好地实现类似的兼容:
void draw_triangle()
{
// Windows triangle drawing code here
// Linux triangle drawing code here
}
其中某个分支的代码会在进行编译之前被去掉,这样编译时就不会出现API未定义的错误。
在C++17中有了新的语法特性if constexpr,我们可以用来替代一部分#if … #else的使用。以下面的使用为例:
void do_sth()
{
log();
// …
}
// ========================>>>
void do_sth()
{
if constexpr (DEBUG_MODE) {
log();
}
// …
}
使用if constexpr的好处是其只会检查语法错误,像宏那样的使用方式,一旦DEBUG_MODE出现typo的错误,编译器是无法准确辨识的。
源码位置
目前几乎所有的断言或者宏会用到宏,比如需要使用__LINE__, __FILE__, __func__ 等定位断言的位置,又或者需要断言开关等等。
要想替代对这些宏的使用则需要用上C++20的std::source_location,该类可以表示关于源码的具体信息,例如文件名、行号以及函数名。
void log(std::string_view message,
const std::source_location& location = std::source_location::current())
{
std::cout << "info:"
<< __FILE__ << ':'
<< __LINE__ << ' '
<< message << '\n';
// ========================>>>
std::cout << "info:"
<< location.file_name() << ':'
<< location.line() << ' '
<< message << '\n';
}
总结
这里提供了一些更“现代”的C++写法来替换不够安全的、使用了宏定义的老式代码,事实上C++的发展过程中一直在提出一些减少预处理宏使用依赖的方案。但从目前来看,还是有不少预处理使用无法替换,即便如此,个人认为适当使用宏和合适的,其AST的生成功能是非常强大的工具,并且某种情况下能使得代码更加易读。
参考资料
1. 《cppcon 2019——Are We Macro-free Yet?》
2. 《Rejuvenating C++ Programs through Demacrofication》
3. 《The year is 2017 - Is the preprocessor still needed in C++》
长
按
关
注
码著
学点有意思的东西.....
以上是关于C++系列学习——Macro Free的主要内容,如果未能解决你的问题,请参考以下文章
具有 GCC 优化的 C++ 代码导致核心在字符串上具有无效的 free()
Anchor free系列网络之YOLOX源码逐行讲解篇--解耦Head的搭建及分析