乐于分享
好东西不私藏

c++编程实践—宏和模板

c++编程实践—宏和模板

一、宏和模板

对于经常使用C/C++编程的小伙伴来说,宏和模板算是“大宝天天见”了。特别在早期的代码中,但凡稍微复杂或有名气一些的代码都会有大段大段的宏处理的模块。虽然大家都明白宏的缺点,但在有些时候儿它确实也“香”。不得不使用它来完成一些复杂的,特别是技巧性很强的工作。
宏是一种预编译器指令,它意味着简单的文本替换,这是它的优势,更是它的劣势。复杂的代码需要宏,但宏的复杂性又难于调试和复现错误。这就需要开发者自己仔细权衡了。宏的最强的地方在于灵活,灵活到很多人都觉得无法理解但程序却运行的很好。另外其也具有一定的扩展能力,这使得它在跨平台中更有优势。但宏的缺点亦是相当明显,它几乎和C++提倡的一系列技术概念都不兼容,如名空间、错误处理。它还无法看到实际生成的代码导致了代码维护的困难。
模板作为C++编程的底层库的必备技术,是C++的精髓所在。它同样是在编译期处理相关的代码生成,但模板提供了相对于宏更加完善的错误和异常处理机制,它也更符合C++语言本身的特点和发展方向。所以,模板技术在C++新标准的不断迭代中,也在不断的前进。但模板也有其另外一面,比如增加了编译时间,代码膨胀。但更难的在于代码的复杂性特别是元编程中,更让开发者闹心。

二、区别和联系

在现代的C++编程中,一般是不建议使用较为复杂的宏来进行开发。倾向于转向模板技术,这样更容易进行代码的安全控制和调试。宏和模板的共同点在于:

  1. 都是在编译期进行处理
    宏是在预处理阶段,也算编译期。而模板则是在编译解析时期。
  2. 可以进行代码复用
    其实宏的主要功能就是实现通用代码;而模板也几乎类似。但模板会更强大
  3. 二者在某些情况下可以互相替换
    在一些通用函数生成如求极值等的情况下,二者可以互相替换
  4. 元编程中经常是模板和宏一起应用
    这个在前面的学习中应该是反复分析过,宏和模板一起在元编程中产生了很强的能力

三、二者的不同在于:

  1. 类型的安全检查
    宏没有安全检查,只是单纯的替换;而模板提供了一系列的相关类型控制和推导机制等
  2. 作用域管理
    宏一般是全局作用域;而模板则严格遵守C++中的作用域管理
  3. 编译问题
    宏的编译友好性相当差,一般很难通过编译器定位;模板则是编译器虽可检查,但过于复杂
  4. 错误处理
    宏本身不支持安全控制和异常处理;模板支持,如概念等
  5. 调试的友好程度
    宏基本无法断点调试(太简单的除外);但模板一般可以像普通代码一样进行调试
  6. 代码的可维护性
    宏的代码是一种替换原则,稍微复杂的替换往往让维护者难于看到代码的实际情况;模板虽然同样复杂,但一般代码最终的形态还是可以看到的

宏和模板都是在编译期处理的,所以一般来说,如果不考虑代码本身的问题,其编译产生的结果,都不会影响到运行时的效率。

四、应用如何选择

在明白了宏和模板的应用后,就可以根据开发者的具体环境进行选择。正所谓没有哪种技术最好,只有哪种技术最合适。对于宏和模板可以通过下面的几个方面进行考量:

  1. 根据实际应用场景确定使用宏和模板。在绝大多数情况下,优先考虑使用模板。尽量避免引入宏
  2. 对于必须使用宏,如预编译条件等情况下,一定要慎重处理
  3. 要深刻理解环境提供的相关宏的系统定义如__FILE__、__LINE__等的使用
  4. 对于编译时间和代码膨胀敏感的情况下考虑使用宏
  5. 如果有可能,尽量减少二者的使用

五、扩展

在C++11后,constexpr的出现,让它与这二者也有一定的联系。比如它可以把宏变量或函数定义转成constexpr,它与模板一样也可以进行类型检查等安全机制管理。而且它的代码明显可见,更容易维护。constexpr在元编程时可以更好的和模板一起实现编译期计算或展开。
通过简单的分析,可以发现constexpr与宏和模板在很多领域都有重合和可替换的场景,这就需要开发者在实际开发中,多使用新标准中的新技术点。尽量减少宏的应用。
然而,技术往往会因为组合产生强大的功能。所以,根据情况将这些技术进行有机的融合,才是开发者最优先掌握的基础技能。

六、例程

看一下相关的代码:

//1. 阶乘
//宏
#define FACTORIAL(n) ((n) <= 1 ? 1 : (n) * FACTORIAL((n)-1))
//模板
template<int N>
structFactorial {
staticconstint value = N * Factorial<N-1>::value;
};

template<>
structFactorial<0> {
staticconstint value = 1;
};

int arr[Factorial<6>::value];   
//constexpr
constexprintfactorial(int n){
return (n <= 1) ? 1 : n * factorial(n - 1);
}
constexprint val = factorial(6); 
//2. 普通函数
//宏
#define MAX(a, b) ((a) > (b) ? (a) : (b))  //此处注意看是否有问题
//模板
template <typename T>
max(T a, T b){  
return (a > b) ? a : b;
}
//constexpr
constexprintmax_int(int a, int b){
return (a > b) ? a : b;
}
//3. 变量定义
//宏
#define SIZE 10
//模板
template<typename T>
constexpr T pi = T(3.1415926535897932385L);
//constexpr
constexprint size = 10;

上面的代码很简单,一看就明白了。

七、总结

再好的技术也需要开发者具体的权衡使用而不能乱用。只要能以最小的代价完成实际的项目功能,达到设计的目的,完成既定的任务。这就是技术选择的原则和方法。