Macro Free In Cpp
One of C++'s aims is to make C's preprocessor redundant because I consider its actions inherently error prone
背景
C预处理器本质是一个文本替换工具,用来在实际编译之前进行一定的预处理操作,一般情况下#开头的预处理操作并不认为是语言本身的一部分,因为编译器永远看不到这些宏定义符号。
以C++来说,用宏的目的并不是出于性能的缘由,更多的只是为了减少重复的代码和进行条件编译。随着modern cpp的发展,越来越的新特性加入使得对宏的使用依赖进一步降低。本文将关注如何使用C++新特性替换C预处理程序。
如何替代宏的使用
- 表达式别名
有一些宏定义会用在表达式别名,替换后的文本会被识别为C++表达式,对于这种情况比较简单的是使用常量表达式或者lambda替换宏,
1 |
|
- 类型别名
类型别名是一个类似于对象的宏,其替换文本可以识别为C ++类型表达式。对于这种,可以使用C++的别名声明来替换:
1 |
|
- 参数表达式
参数表达式是一种类似于函数的宏,替换文本后会扩展为表达式或语句。对于这种使用,C++中的最佳实践是使用内联模版函数。
1 |
|
这里引入了内联,自动的推导类型和完美转发等modern c++的特性。完美转发使得调用方可以根据需要决定参数传递的类型。
- 参数化类型别名
这种其实就是模版别名,在C++11之前需要用宏去实现。
1 |
|
- 条件编译
目前绝大多数开源的C++项目都会依赖宏来进行条件编译,其本质意义是通过定义宏与否来改变某个定义/声明。
比如存在一个绘制三角形的API,但其具体实现会根据操作系统而变化,通过预处理器就可以很好地实现类似的兼容:
1 | void draw_triangle() |
其中某个分支的代码会在进行编译之前被去掉,这样编译时就不会出现API未定义的错误。
在C++17中有了新的语法特性if constexpr
,我们可以用来替代一部分#if … #else
的使用。以下面的使用为例:
1 | void do_sth() |
使用if constexpr
的好处是其只会检查语法错误,像宏那样的使用方式,一旦DEBUG_MODE
出现typo的错误,编译器是无法准确辨识的。
当然if constexpr
的使用也是有其不足之处的,以上面的draw_triangle
函数为例,即便某个条件分支不会被使用,你仍然需要有相关冗余的声明。所以对于这种情况,个人建议还是不需要使用if constexpr
替代宏。
- 源码位置
目前几乎所有的断言或者宏会用到宏,比如需要使用__LINE__, __FILE__, __func__
等定位断言的位置,又或者需要断言开关等等。
要想替代对这些宏的使用则需要用上C++20的std::source_location
,该类可以表示关于源码的具体信息,例如文件名、行号以及函数名。
1 |
|
总结
这里提供了一些更“现代”的C++写法来替换不够安全的、使用了宏定义的老式代码,事实上C++的发展过程中一直在提出一些减少预处理宏使用依赖的方案。但从目前来看,还是有不少预处理使用无法替换,即便如此,个人认为适当使用宏和合适的,其AST的生成功能是非常强大的工具,并且某种情况下能使得代码更加易读。
参考资料
- 《cppcon 2019》——https://www.youtube.com/watch?v=c6NkeF1eChs
- 《Rejuvenating C++ Programs through Demacrofication》——https://www.stroustrup.com/icsm-2012-demacro.pdf
- 《if statement》——https://en.cppreference.com/w/cpp/language/if
- 《The year is 2017 - Is the preprocessor still needed in C++?》——https://foonathan.net/2017/05/preprocessor/