Catch2 如何在禁用异常的编译环境下使用?
【免费下载链接】Catch2A modern, C++-native, test framework for unit-tests, TDD and BDD - using C++14, C++17 and later (C++11 support is in v2.x branch, and C++03 on the Catch1.x branch)项目地址: https://gitcode.com/GitHub_Trending/ca/Catch2
如果你的目标平台(嵌入式系统、部分 RTOS 工具链等)关闭了 C++ 异常,而 Catch2 默认用异常向测试框架报错、并在REQUIRE系列断言失败时中止测试,那么直接编译就会出问题。Catch2 提供了一套编译期配置来支持这种场景:文档将其标注为实验性支持(自 Catch2 2.4.0 引入),核心做法是给测试目标加上无异常编译选项,必要时再显式定义CATCH_CONFIG_DISABLE_EXCEPTIONS或自定义异常处理函数。
先明确禁用异常后的两个行为变化(来自 configuration.md 的 “Disabling exceptions” 一节):
- 原本以异常形式报告的错误,其消息会写到
Catch::cerr,随后调用std::terminate; REQUIRE系列宏的断言失败时,当前 reporter 返回之后会调用std::terminate。
也就是说,CHECK系列断言照常运行并汇报,但任何“需要抛异常”的路径都会直接终止进程。
先弄清哪些宏控制行为
docs/configuration.md 给出了三个相关宏:
| 宏 | 作用 |
|---|---|
CATCH_CONFIG_DISABLE_EXCEPTIONS | 强制以无异常模式编译。Catch2 也能自动检测编译器是否禁用了异常,此宏用于强制 |
CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER | 启用自定义处理点:由用户提供Catch::throw_exception的定义,替代默认的“写Catch::cerr后std::terminate” |
CATCH_CONFIG_NO_DISABLE_EXCEPTIONS | 反向开关,取消自动检测出的无异常模式 |
自动检测逻辑见 catch_compiler_capabilities.hpp 第 375–379 行:当内部检测标记、CATCH_CONFIG_DISABLE_EXCEPTIONS与CATCH_CONFIG_NO_DISABLE_EXCEPTIONS都未定义时,Catch2 自行定义CATCH_CONFIG_DISABLE_EXCEPTIONS。所以只要整个构建确实禁用了异常,通常不写宏也能工作;显式定义只是消除歧义。
无异常模式下 Catch2 的try/catch宏会被展开为空壳(见 catch_compiler_capabilities.hpp 第 456–464 行):
#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) #define CATCH_TRY if ((true)) #define CATCH_CATCH_ALL if ((false)) #define CATCH_CATCH_ANON(type) if ((false)) #else #define CATCH_TRY try #define CATCH_CATCH_ALL catch (...) #define CATCH_CATCH_ANON(type) catch (type) #endif默认的“替代表抛异常”处理在 catch_enforce.cpp 第 15–22 行:
#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) [[noreturn]] void throw_exception(std::exception const& e) { Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n" << "The message was: " << e.what() << '\n'; std::terminate(); } #endif注意该定义只在“无异常且未定义CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER”时存在——一旦你启用了自定义处理点,就必须自己提供这个函数。
配置无异常编译
仓库自带的 tests/ExtraTests/CMakeLists.txt 第 91–102 行是官方测试目标的做法,可以直接照搬其编译选项:
add_executable(DisabledExceptions-DefaultHandler ${TESTS_DIR}/X03-DisabledExceptions-DefaultHandler.cpp) add_executable(DisabledExceptions-CustomHandler ${TESTS_DIR}/X04-DisabledExceptions-CustomHandler.cpp) foreach(target DisabledExceptions-DefaultHandler DisabledExceptions-CustomHandler) target_compile_options(${target} PUBLIC $<$<CXX_COMPILER_ID:MSVC>:/EHs-c-;/D_HAS_EXCEPTIONS=0> $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:QCC>,$<CXX_COMPILER_ID:AppleClang>>:-fno-exceptions> ) target_link_libraries(${target} Catch2_buildall_interface) endforeach() target_compile_definitions(DisabledExceptions-CustomHandler PUBLIC CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER)移植到自己的项目时,要点是:
- 编译器开关分两类:MSVC 用
/EHs-c-并定义_HAS_EXCEPTIONS=0;Clang / GCC / QCC / AppleClang 用-fno-exceptions。上面用的是 CMake 生成器表达式按编译器 ID 选择,等价的手写形式是 GCC/Clang 下加-fno-exceptions,MSVC 下加/EHs-c- /D_HAS_EXCEPTIONS=0。 PUBLIC不能少:这里的选项是PUBLIC的,目的就是把无异常标志传递到 Catch2 库本身。官方链接的是Catch2_buildall_interface(即从源码构建 Catch2 的接口目标)。如果你链接的是已经用默认选项构建好的预编译 Catch2 库,库内部仍是异常开启状态,与你目标的无异常模式不一致——正确做法是像官方测试一样,把无异常选项传导到 Catch2 的编译中(以源码方式集成 Catch2,或自行用同样选项重编)。- 宏只加在需要的目标上:
CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER只定义在启用自定义处理的目标上(见上方target_compile_definitions行);默认处理目标不定义任何额外宏。
自定义处理点开启后,你需要提供一个定义,官方测试 X04-DisabledExceptions-CustomHandler.cpp 第 13–22 行给出的实现是:
namespace Catch { [[noreturn]] void throw_exception(std::exception const& e) { std::cerr << "====== CUSTOM HANDLER ====== run terminates because an exception was thrown.\n" << "The message was: " << e.what() << '\n'; // Avoid abort and other exceptional exits -- there is no way // to tell CMake that abort is the desired outcome of a test. exit(1); } }函数签名与 docs/configuration.md 的要求一致:[[noreturn]] void Catch::throw_exception(std::exception const&)。你自己的实现可以记录日志、上报或按平台要求退出,但必须最终终止控制流。
用官方测试验证行为
仓库里有一对现成的验证样本:X03-DisabledExceptions-DefaultHandler.cpp(默认处理)和 X04-DisabledExceptions-CustomHandler.cpp(自定义处理)。X03 中有两个测试用例:
"Tests that run":4 条CHECK断言,其中 2 条失败,但测试应完整跑完并汇报;"Tests that abort":第三条REQUIRE失败,预期触发终止。
tests/ExtraTests/CMakeLists.txt 为这两个目标登记了验收正则,也就是判断运行结果是否正确的依据:
| 运行命令 | 期望(PASS_REGULAR_EXPRESSION) | 不应出现(FAIL_REGULAR_EXPRESSION) |
|---|---|---|
DisabledExceptions-DefaultHandler "Tests that run" | assertions: 4 \| 2 passed \| 2 failed | abort;terminate;fatal |
DisabledExceptions-DefaultHandler "Tests that abort"(仅非 Windows 登记) | Catch will terminate | 无 |
DisabledExceptions-CustomHandler "Tests that run" | assertions: 4 \| 2 passed \| 2 failed | ====== CUSTOM HANDLER ====== |
DisabledExceptions-CustomHandler "Tests that abort" | ====== CUSTOM HANDLER ====== | 无 |
按此验证:先按上一节编译出两个可执行文件,再分别运行上面四个命令。默认处理跑"Tests that run"时应看到断言汇总assertions: 4 | 2 passed | 2 failed且没有 abort/terminate 字样——这说明无异常模式下普通的失败断言仍能正常汇报;跑"Tests that abort"时应看到 catch_enforce.cpp 中那条Catch will terminate because it needed to throw an exception.,对应REQUIRE失败后的终止行为。自定义处理则相反:"Tests that run"中不应出现CUSTOM HANDLER输出(CHECK失败不需要抛异常),只有"Tests that abort"才打印====== CUSTOM HANDLER ======并以exit(1)退出。
一个值得注意的细节:X03 的"Tests that abort"用例里先用std::set_terminate把终止行为改成exit(1)(见 X03 第 27 行),原因是 CMake 无法声明“abort 就是期望结果”;而默认处理引发的终止在 Windows 上会触发即时调试器弹窗,所以官方只在该用例上加了if(NOT WIN32)的登记(CMakeLists.txt 第 149–155 行)。如果你在 Windows 上做同样的手动验证,进程终止的表现形式可能不同,这属于文档已说明的平台差异。
限制与边界
- 文档原文称这是experimental support(“We also provide an experimental support for disabling exceptions”),即行为不承诺向后兼容,使用时留意所锁定的 Catch2 版本。
- 两类终止行为是固定语义,不是可配置的:内部错误 → 消息写入
Catch::cerr后std::terminate;REQUIRE失败 → reporter 返回后std::terminate。自定义处理点只能替换“替代表抛异常”的这一个入口,改变不了REQUIRE失败后终止的事实。 CATCH_CONFIG_DISABLE_EXCEPTIONS等配置宏属于编译期配置,和文档中其他CATCH_CONFIG_*宏一样,应通过编译定义统一给到参与链接的所有翻译单元,不要只加在个别文件上。- 文档没有给出性能数据或其他平台的专项说明;本文只覆盖“无异常编译 + 默认/自定义处理 + 运行验证”这一条路径。更多编译期开关(前缀宏、
CATCH_CONFIG_DISABLE等)见 configuration.md。
【免费下载链接】Catch2A modern, C++-native, test framework for unit-tests, TDD and BDD - using C++14, C++17 and later (C++11 support is in v2.x branch, and C++03 on the Catch1.x branch)项目地址: https://gitcode.com/GitHub_Trending/ca/Catch2
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考