一、预编译宏应用
预编译宏的应用非常多,最常见的就是处理宏定义和连接等,包括常见的前面提到的__LINE__等宏。但是有一种宏的应用很多,就是在代码中,对某一段代码需要处理。举一个例子,有一个函数实现读取文件的功能,但这个函数要在Windows和Linux上使用,那么就可以写两类代码,用一个编译宏来控制,通过对不同操作系统预定义宏的判断来决定使用哪种实现。另外还一种比较常见的应用方式就是处理C++和C接口函数时,处理C++的改名问题使用extern “C”。
但这里都有一个问题,就是上面这两种情况,其实都是通过系统早已经定义好的宏来处理,那么编译时就不需要做任何动作。可实际情况上还有一类应用,就是仍然需要两种模块,需要通过编译时由编译者来动态决定使用哪类,这时候儿怎么办好呢?
二、编译时处理
针对上面的问题,其实有很多种解决方法,这里只提使用自定义预编译宏来处理的情况。就是开发者在开发时定义一个宏开关变量,通过在编译时动态输入这个宏开关变量的值来决定使用哪类模块的方式。类似于:
#ifdef ABC
...
#else
...
#endif
上面的意思是在编译时给ABC输入定义来决定使用哪块代码段,从而完成指定的功能。
这里分析一下常见的两种方式:
1、直接处理
一切故事都得从最初开始讲才好讲明白,看下面的代码:
#include <iostream>
int main()
{
int num = 0;
#ifdef LEFT
std::cout<<"cur left pos:"<<num+10<<std::endl;
#else
std::cout<<"cur rigth pos:"<<num+1<<std::endl;
#endif
return 0;
}
很简单的一个例子,使用下面的两种方式分别编译执行:
//第一种
g++ -o m macro.cpp
//结果:
cur rigth pos:1
这是最基本的编译方式,得到的结果当然是没有定义LEFT的情况。
还有另外一种:
第二种:
g++ -o mm -DLEFT macro.cpp
或g++ -o mm -DLEFT=1 macro.cpp
//结果
cur left pos:10
此时,在编译时传进了LEFT宏的定义,则开始执行宏编译开关的另外一种情况。-D是一种处理预编译宏传参的方式,它经常用到的时候儿是在处理一些编译选项时用得很多。需要注意的是,在Windows平台可能会使用/D,这个需要在不同情况下处理一下。
2、CMake中的处理
但是,在CMake的开发中,情况又有了些改变,仍然使用上面的代码,但是需要在CMakeLists.txt中增加相关的编译选项:
if(LEFT EQUAL 1)
add_compile_definitions(LEFT)
endif()
#如果是字符串
if(${LEFT} STREQUAL ok)
add_compile_definitions(LEFT)
endif()
但是在编译时会有一个问题:
cmake -B build -DLEFT=1 #必须赋值,否则报错,因为在上面等于1。可以使用别的方式来实现不赋值操作
在低版本中,处理有可能也会有一些不同(add_compile_definitions是3.12后增加的,以前可以用add_definitions)。
另外如果工程中这种预编译宏非常多,可以使用下面的方式:
option(LFET "option for left" OFF)
if (LFET) # LFET是CMake中的变量
add_definitions(-DLFET) #上文中C++中的代码中宏定义
endif(LFET)
如果模块划分颗粒很粗大(比如库),可以:
IF(LFET)
find_package(ABC REQUIRED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ABC_CXX_FLAGS}")
ENDIF(LFET)
至于add_definitions(-DFOO)、 add_compile_definitions(FOO)和 target_compile_definitions(target PUBLIC FOO) 有什么不同,很好理解,前两个是自此以下全都定义,而最后一个是只对具体目标定义。cmake发展到现在已经有些大了,再加上新老版本的不同,一定要注意。
三、总结
总之,应用是根本。有啥不清楚的多翻翻官网的手册,其实很多人不是没听说过某些命令,主要是经验不足导致无法组合的使用某些命令来完成一些具体的工作。工具就是这样,不像技术需要思考和反复验证。工具就看你用得熟悉不熟悉,用得时间长了,自然就熟悉了,这和开车没有什么大的区别。