4. option命令:option()定义BOOL型缓存选项,与set() + BOOL + CACHE的对比

好,咱们来聊聊 option() 这个命令。说实话,很多新手甚至一些老手,都觉得它就是个语法糖。但我在项目中踩过几次坑之后,发现事情没那么简单。

4.1 先看个最直观的例子

你想想看,如果我想让用户决定要不要开启某个实验性功能,最直接的方式是什么?

# 方式一:用 option()
option(USE_EXPERIMENTAL_FEATURE "启用实验性功能" OFF)

# 方式二:用 set() + BOOL + CACHE
set(USE_EXPERIMENTAL_FEATURE OFF CACHE BOOL "启用实验性功能")

嗯,从功能上看,这两行代码的效果几乎一模一样。它们都会在 CMake 缓存中创建一个 BOOL 类型的变量,默认值是 OFF,用户可以通过 cmake -DUSE_EXPERIMENTAL_FEATURE=ON 来修改。

那为什么还要搞两个写法?

4.2 核心区别:谁说了算?

我个人习惯用 option(),因为它有一个非常重要的特性——它不会覆盖用户已经设置的值。说白了,就是「用户优先」。

举个例子:

# CMakeLists.txt
option(MY_FLAG "我的标志" ON)

# 用户第一次构建时
cmake -DMY_FLAG=OFF ..
# 此时 MY_FLAG = OFF

# 用户第二次构建时,即使不传 -D
cmake ..
# MY_FLAG 仍然是 OFF,不会变回 ON

为什么会这样?因为 option() 内部逻辑是:如果缓存中已经有这个变量了,就别动它。只有第一次构建,或者缓存被清空时,才会用默认值。

而 set() + CACHE 呢?它默认会强制覆盖缓存中的值。除非你加上 FORCE 参数,但那就更暴力了。

核心结论:

  • option():尊重用户选择,不覆盖已有缓存
  • set() + CACHE:每次运行都会覆盖缓存(除非用 FORCE 控制)

4.3 避坑指南:我曾经犯过的错

我曾经在一个大型项目中,用 set() + CACHE 定义了一个调试开关:

set(ENABLE_DEBUG_LOG ON CACHE BOOL "启用调试日志")

结果每次我同事重新 cmake 配置时,这个开关都会被重置为 ON。他明明已经改成 OFF 了,但一重新配置又变回去。气得他差点把键盘砸了。

后来我换成 option(),问题就解决了。所以,如果你想让用户能自由切换,就用 option()。

4.4 什么时候用 set() + CACHE?

那 set() + CACHE 就一无是处吗?也不是。我建议在以下场景用它:

  • 你想强制某个值,比如项目必须用 C++17,你可以写:set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++标准")。这样即使用户想改,也会被覆盖回去。
  • 你需要非 BOOL 类型,比如 STRING、FILEPATH、PATH 等。option() 只支持 BOOL。
  • 你想在子目录中定义缓存变量,并且希望它影响父目录。option() 的作用域有点微妙,而 set() + CACHE 更可控。

小技巧:如果你用 set() + CACHE,但又想保留用户的选择,可以加个判断:

if(NOT DEFINED CACHE{MY_VAR})
  set(MY_VAR "default" CACHE STRING "描述")
endif()

这样只有第一次才会设置默认值,之后用户改了就保留。

4.5 对比表格:一目了然

特性 option() set() + BOOL + CACHE
支持类型 仅 BOOL 所有类型(BOOL, STRING, PATH...)
覆盖行为 不覆盖已有缓存 默认覆盖(除非加 FORCE)
代码简洁度 高 低
适用场景 用户可选的开关 强制配置、非 BOOL 类型

4.6 一个实际项目中的选择

我记得有一次做嵌入式项目,需要让用户选择是否启用浮点运算加速。我用了 option():

option(ENABLE_FPU_ACCELERATION "启用硬件浮点加速" ON)

if(ENABLE_FPU_ACCELERATION)
  target_compile_definitions(my_app PRIVATE USE_FPU=1)
  target_link_options(my_app PRIVATE -mfloat-abi=hard)
else()
  target_compile_definitions(my_app PRIVATE USE_FPU=0)
  target_link_options(my_app PRIVATE -mfloat-abi=soft)
endif()

这样用户可以在不同硬件版本之间自由切换。如果用 set() + CACHE,每次重新 cmake 都会重置为 ON,那用户就疯了。

4.7 总结一下

说白了,option() 就是 set() + BOOL + CACHE 的一个「用户友好版」。它帮你处理了「不要覆盖用户选择」这个逻辑。除非你有特殊需求(比如强制某个值、或者需要非 BOOL 类型),否则我建议你无脑用 option()。

嗯,这里要注意:option() 的默认值必须是 ON 或 OFF,不能写 TRUE 或 FALSE,虽然也能用,但不符合规范。我见过有人写 option(XXX "描述" TRUE),结果 CMake 直接报错。

最后送你一句话:能用 option() 就别用 set() + CACHE,除非你真的知道自己在干什么。