宏和模板的对比——预编译和编译的较量
这篇文章通过宏与模板的对照实验,解释了预处理、编译和链接三个阶段里“文本替换”和“类型约束”的本质差异。预处理阶段的关键观察通过 gcc -E 展示宏调用会直接展开成文本,宏定义本体在输出中消失。同样观察模板代码时,模板定义不会在预处理阶段展开,说明实例化发生在后续编译阶段。字符串字面量示例强调了宏缺乏类型检查时的典型…
本文默认你已经拥有基本的gcc编译选项知识,如果没有,可以看看这篇文章 程序的编译过程gcc版。 从预编译的角度对比宏定义和模板来测测宏定义 大家都知道,宏定义仅仅只作用于文本的替换,在预编译的时候,把用到宏定义的部分替换为真正的文本而已,缺点就是不会做类型检查,只要语法能过编译就行。 我们定义一个宏来求两者之间的最大值,为了防止预编译的代码太过冗长不易看懂,没有用 include 去包含其他函数库的声明。 代码如下:#define MAX_VALUE(_1,_2) ((_1>_2)?_1:_2) int main(){ MAX_VALUE(1,2); } 经过 gcc -E 命令后得到预处理后的代码如下:# 0 ".\\test_template.cpp" # 0 "<built-in>" # 0 "<command-line>" # 1 ".\\test_template.cpp" # 10 ".\\test_template.cpp" int main(){ ((1>2)?1:2); } 观察以上代码,我们发现,确实是直接的文本替换,前面的宏定义代码都不见了,只有,传入的替换文本了。宏定义的害处 如果我们把下面这段代码传入到宏定义中:#define MAX_VALUE(_1,_2) ((_1>_2)?_1:_2) int main(){ MAX_VALUE("abdcdf","fdf"); } 很明显,这个代码是可以过编译的,但我们肯定不想直接的如下的方式进行字符串的比较,这样的比较毫无意义,只不过是比较的地址而已。# 0 ".\\test_template.cpp" # 0 "<built-in>" # 0 "<command-line>" # 1 ".\\test_template.cpp" # 10 ".\\test_template.cpp" int main(){ (("abdcdf">"fdf")?"abdcdf":"fdf"); } 故宏定义的最大危害,就是没法对类型进行检查!这就导致无法灵活的进行优化和调整一些直接文本替换带来的副作用。模板是否会进行预处理操作? 源代码:template<typename T> T MAX_VALUE(T _1, T _2){ if(_1<_2) return _2; return _1; } int main(){ MAX_VALUE("abdcdf","fdf"); } 预处理后:# 0 ".\\test_template.cpp" # 0 "<built-in>" # 0 "<command-line>" # 1 ".\\test_template.cpp" template<typename T> T MAX_VALUE(T _1, T _2){ if(_1<_2) return _2; return _1; } int main(){ MAX_VALUE("abdcdf","fdf"); } 我们发现模板并不会在预处理时被展开,它甚至不会和预处理的调用部分形成任何联系! 看来模板的展开处理过程是发生在后续的编译过程中,由于本人不清楚如何反汇编和反编译过程,故没有亲身实践。 但从我的日常使用体验和大佬们总结的经验看来,模板也和宏定义的展开类似,就是简单的文本替换,但可以利用编译时期的语法检查和类型判断(type_traits技术),所以能够对不同情况的展开进行不同的处理。 比如上面的代码利用偏特化进行优化:#include <iostream> #include <cstring> template<typename T> int MAX_VALUE(T _1, T _2){ if(_1<_2) return _2; return _1; } using type = const char *; template<> int MAX_VALUE<type>(type _1, type _2){ std::cout << "调用偏特化"; return strcmp(_1, _2); } int main(){ MAX_VALUE("abdcdf","fdf"); } 善用编译期的模板为什么大多数模板库声明和定义都放在一起?声明和定义分开处理的原因 我们清楚,声明和定义中,声明可以有无数份,但定义在一个项目里只能存在一份!所以我们在多文件项目的过程中,需要 include<xxx.h> 得到对应的声明,最后再把对应的实现 xxx.c 编译完后,再通过链接过程让声明能够连接到对应的定义来进行使用。所以在通常情况下我们都是在 .h 文件里面进行声明,再写一个 .c 文件实现定义,把声明和定义进行分开,这样无论你在其他的 .c 文件里使用多少个 include 操作,整个…
正在初始化 WebAssembly 引擎…
首次编译原生模块可能需要数秒
就绪后,页面交互将以接近原生的速度运行