写点什么

宏在 C++ 中的替代解决方案

用户头像
老王同学
关注
发布于: 2020 年 05 月 24 日

宏,在C语言中是个神的存在,能够玩出各种花样,也正因为此,才会给普通程序员造成不少的困扰。由于宏只在预编译阶段起作用,使得编译器无法检测其中的Bug,作为新时代的C++程序员,还是远离的好。



C++为宏提供了一些替代的解决方案,嗯,是一些。



1. 常量定义

#define NUM 100

《EffectiveC++》的第一个条款,讨论的就是这个宏。由于宏是预编译程序来处理,所以NUM这个名字不会加入到符号表中,如果出现编译错误时,提示信息中就不会出现NUM,而是100,为排除错误增加了额外的障碍。



替代方案就是使用const来定义常量,或者使用枚举enum。

const int NUM = 100;



const常量放在头文件中,也不必担心存在多个实例的问题,对于const修饰的变量,编译器一般也会对其进行优化,不会出现多重定义的问题。



C语言中还有一个特殊的常量定义:NULL。其一般的定义为 #define NULL 0,指针的内容却是一个整型,这不符合常理。所以在C++11中使用nullptr代替了NULL。



2.函数定义

由于宏只是在代码中做字符串替代展开,所以,用宏定义的函数,实际上并没有减少代码的体积。另外,还有一些天然的缺陷,假设一个求平方的函数宏



#define square(x) (x*x)
void f(double d, int i)
{
square(d); //OK
square(i++); //糟糕, (i++*i++)
square(d+1); //更糟,(d+1*d+1)
}



纵然可以把参数加上括号来解决,#define square(x) ((x)*(x)),但i++被执行两次这个问题还是无法解决。



C++中的替代方案,就是使用inline函数。

inline int square(intvalue)
{
return value*value;
}



inline函数具有函数的性质,参数传递不管是传值还是传引用,都不会对参数进行重复计算;同时会对参数做类型检查,保证代码的正确性;inline函数也是在代码中做代码展开,效率上并不比宏逊色。



如果整型不满足需求,还可以定义为模板:

template<class T>
inline T square(T& value)
{
return value * value;
}



还有一种更离谱的函数定义形式: 

#define Null_Check(p)\
if(p == NULL) return;



这种使用反斜杠定义的函数,更得注意,如果在反斜杠后多了个空格的话,有的编译器会出现变异错误,而提示信息嘛,你可能会困扰很久的。因为反斜杠会对空格这个字符做反义,就会在代码中的引入非法字符,人眼是很难发现这种错误的。



3.类型重定义

#define DWORD unsigned int



这种类型重定义完全可以使用 typedef unsigned int DWORD 来替代。



4.条件编译

#ifdef SystemA
testA();
#else //SystemB
testB();
#endif



这种条件编译宏,一般在不同的产品或平台使用同一套代码的情况,大量出现。定义了SystemA的时候,SystemB的代码是不编译的,也就意味着你的代码没有时刻处于编译器的监控中。可以使用template技术来解决。

constint SystemA = 1;
constint SystemB = 2;
template<int T>
void test()
{}
//定义不同的系统的特化版本
template<> void test<SystemA>(){ //SystemA的实现 }
template<> void test<SystemB>(){ //SystemB的实现 }



这样,不同的系统使用自己的模板即可,别人的代码也会同时接受编译器的检查,不至于出现遗漏编译错误的情况。



5.头文件包含

#ifndeftest_h
#definetest_h
//test.h的实现
#endif



为了防止头文件重复包含,C++中现在也只能这么做,目前没有别的替代方案。且看看原委,Bjarne Stroustrup在《C++语言的设计与演化》一书中,提供了一个include的设计,可惜的是并没有真正实现。(Cpp, 即C语言预处理器)



我曾经建议可以给C++本身增加一个include指示字,作为Cpp的#include的替代品。C++的这种include可以在下面三个方面与Cpp的#include不同:



1)如果一个文件被include两次,第二个include将被忽略。这解决了一个实际问题,而目前这个问题是通过#define和#ifdef,以非常低效而笨拙的方式处理的。



2)在include的正文之外定义的宏将不在include的正文内部展开。这就提供了一种能够隔离信息的机制,可以使这些信息不受宏的干扰。



3)在include的正文内容定义的宏在include正文之后的正文处理中不展开。这保证了include正文内部的宏不会包含它的编译单位强加上某种顺序依赖性,并一般地防止了由宏引起的奇怪情况。



对于采用预编译头文件的系统而言一般地说,对于那些要用独立部分组合软件的人们而言,这种机制都将是一个福音。请注意,无论如何这还只是一个思想而不是一个语言特征。



也就是说,这个想法在C++中并没有实现。



结论: 如果你没有很好的驾驭宏,那就敬而远之吧。



发布于: 2020 年 05 月 24 日阅读数: 107
用户头像

老王同学

关注

还未添加个人签名 2020.04.30 加入

还未添加个人简介

评论 (2 条评论)

发布
用户头像
您好,这篇文章是您的原创吗?老王同学和忘忧草是否是同一人?
2020 年 05 月 25 日 12:08
回复
是的.
2020 年 06 月 25 日 09:42
回复
没有更多了
宏在C++中的替代解决方案