宏的妙用
作者 阿荣
1、概述
C++中出了const关键字以后,宏定义常量的功能已经不在被推荐使用。这使得宏似乎没有了用武之地。实际上,宏还可以做很多事情,笔者也难以全部列举。这里,仅仅列举几个典型的用法,希望大家能够从中获益。
2、实现多环境兼容
常见的情况是,我们实现了一个函数,希望它只在某种编译条件满足是被编译和使用。例如,我希望在源码中插入调试语句,以便以Debug方式运行时能够通过调试信息观察程序运行情况。但是,在产品发售给用户时,我又希望这些调试信息不要输出,以降低代码尺寸,提高运行性能。这一问题的解决方法就是使用宏。根据条件编译指令,对于不同的编译条件,提供不同的实现。例如:我们希望在特定的位置向日志中写入当前行号和文件名,以判断对应代码是否被执行到,可以使用下面的宏:
#ifdef _DEBUG #define TRACE_FILE_LINE_INFO() do{ CString str; str.Format(_T("file=%s,line=%urn",__FILE__,__LINE__); CFile file("logfile.txt"); file.Write(str,str.GetLength()); }while(0) #else #define TRACE_FILE_LINE_INFO() #endif上面这段代码通过#ifdef #else #endif三个条件编译指令,根据_DEBUG定义情况(该宏用于区分DEBUG版本和Release版本),决定了具体的TRACE_FILE_LINE_INFO宏函数的实现。使用者可以用如下方法使用
TRACE_FILE_LINE_INFO();//这里显示行号和文本信息当然,采用其他方式也可以实现这一功能,但是使用宏有以下特殊好处: 只有需要的代码才会被编译,减少了符号表的尺寸,也减少了代码尺寸 宏在编译时被展开,因此用于表示代码位置的__FILE__,__LINE__宏可以起作用,如果用函数实现,这两个宏则不能起作用。
3、用新函数替换原有函数
对于一个设计好的函数,假设它已经在一个很大的工程中到处使用,突然发现它的一个不足,想修改它的功能。也许这个新增加的功能需要一个额外的参数,但是又不想修改使用这些函数的地方。假设有两个函数必须成对使用,一个占用资源并使用,另外一个则释放资源以供其他模块使用。典型的例子是,函数一(假设为Lock)获得一个全局的锁,这个锁用于保护在多线程情况下多个线程对一个公共资源如一个全局变量的访问。问题是,这个Lock函数获得锁以后,其他线程将不能再获得这个锁,直到当前线程释放这个锁。编制Lock函数的程序员同时提供了一个 Unlock函数用于释放锁,并要求使用Lock的人必须对应的使用Unlock。调试程序时,发现线程被死锁,怀疑有人使用完Lock后忘记调用 Unlock,但是Lock和Unlock在这个大工程中都被广泛的使用,因此设计者希望Lock和Unlock都增加两个额外的参数file和line,以说明这两个函数在哪里被调用了,哪些地方被死锁以及哪些地方调用了Lock但是没有调用Unlock。假设这两个函数的原型为:
void Lock(); void Unlock();新设计的函数的原型是:
void Lock(LPCTSTR szFileName,UINT uLineNo); void Unlock(LPCTSTR szFileName,UINT uLineNo);设计完新的函数后,项目经理希望所有模块统一使用这两个函数并提供文件名和行号信息作为参数。这样将是一个非常浩大且烦琐的工作,意味着重复性的劳动、数小时无聊的加班和工期的延误,这是谁都不愿意遇到的。使用宏可以非常轻松的解决这一切。首先,应该把新设计的函数换个名字,不妨叫它们NewLock和NewUnlock,也就是他们的原型为:
void NewLock(LPCTSTR szFileName,UINT uLineNo); void NewUnlock(LPCTSTR szFileName,UINT uLineNo);这个函数原型应该放在一个头文件中,避免在多个地方重复的声明。需要用到这两个函数的cpp文件,只要包含他们原型所在的头文件即可。为了不改动使用Lock/Unlock函数的模块,在头文件中增加如下两行:
#define Lock() NewLock(__FILE__,__LINE__) #define Unlock() NewUnlock(__FILE,__LINE__)这样,当不同模块使用这个函数时,宏替换功能在编译时起作用,自动使用了__FILE__和__LINE__为参数,调用了新设计的函数。调试的时候就可以根据日志来判断什么地方遗漏了调用Unlock。
4、给一个函数捆绑其他功能
上述方法修改了原来函数的设计。实际上,这两