(点击上方公众号,可快速关注)
C语言引入的宏支持了一定程度的元编程,但它仅仅是简单的字符串替换,这种“六亲不认”的操作很容易导致一些编译错误。
这篇文章介绍了一种场景:项目同时引入了老的C头文件,里面用宏定义了一些宏函数;还引入了C++的头文件,里面用其他方式定义了一些同名函数。具体到问题本身,这个老的头文件是Windows.h
,它里面用宏定义了max
函数,C++的标准库的algorithm
也定义了max
函数。
问题重现
简单的示例程序如下:
#include <Windows.h>
#include <algorithm>
int main()
{
int m = std::max(5, 6);
}
编译时,会报错:
先看一下Windows.h
中的max
宏长啥样子。实际上max
宏定义在minwindef.h
头文件,而Windows.h
引用了minwindef.h
。max
宏定义如下:
#ifndef NOMINMAX
#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
#endif /* NOMINMAX */
再来分析出错的原因,是程序里的max
被认为是Windows.h
中的宏,会进行替换,这样std::max(5, 6)
会替换成:
std::(((5) > (6)) ? (5) : (6));
这不是合法的C++程序,范围解析运算符后不是合法的名称,所以报以上的错误。使用min
的时候也会出现类似情况。
引入Windows.h
而引起max
或min
冲突这个问题在Windows下开发挺常见的,因为很多程序都会直接或间接引用Windows.h
头文件。
解决方案
对于以上问题的解决方案,从宏定义的通用性和特殊性两方面着手。通用性,需要看C/C++的宏机制有哪些支持可以取消宏的替换。特殊性,需要看Windows.h
有特殊的开关可以关闭max/min
宏的替换。
方案一
使用括号括起std::max
避免宏替换:
#include <Windows.h>
#include <algorithm>
int main()
{
int m = (std::max)(5, 6);
}
方案二
见招拆招,取消max
定义。
#include <Windows.h>
#include <algorithm>
#undef max
int main()
{
int m = std::max(5, 6);
}
方案三
定义NOMINMAX
宏,特定于max/min
场景。
#include <Windows.h>
#include <algorithm>
#define NOMINMAX
int main()
{
int m = std::max(5, 6);
}
这三种方案各有优缺,考虑到通用性上,方案一和方案二较好,缺点是必需把每个引起冲突的宏处理一遍;考虑到具体问题具体分析的原则,方案三较好,能批量处理相关的宏。在实际的项目中,我使用了方案二,毕竟引起冲突的宏并不多。