
std::source_location 在测试日志里为什么总打不出文件行号?
因为默认构造的 std::source_location::current() 只在调用点展开,如果封装成普通函数再调用,就会固定指向那个函数内部,而不是测试用例所在位置。这是最常踩的坑——你以为日志里写了 test_foo.cpp:42,结果全是 logging_utils.cpp:15。
正确做法是把 std::source_location 当作函数默认参数,且必须用 = std::source_location::current() 形式声明,让编译器在每个调用点自动注入真实位置:
void log_test_start(const char* msg, std::source_location loc = std::source_location::current()) {
printf("[%s:%d] %s\n", loc.file_name(), loc.line(), msg);
}调用时不用传参:log_test_start("checking edge case") → 输出就是你写这行的位置。
- 不能写成指针或引用传递(会丢失调用点信息)
- 不能在宏里二次转发(比如
#define LOG(...) log_test_start(__VA_ARGS__)),否则又塌缩到宏定义处 - Clang 13+、GCC 12+、MSVC 2019 16.8+ 才真正稳定支持;旧版本可能返回空字符串或 0 行号
如何让 ASSERT_EQ 自动带 source_location 而不改每条断言?
直接重载或包装 ASSERT_EQ 不现实——它本质是宏,展开后包含 return 和标签跳转。更可行的是用 RAII + 析构日志,在测试函数入口自动记录起点,并在失败时由断言宏触发日志增强。
立即学习“C++免费学习笔记(深入)”;
实际建议:绕过宏改造,改用自定义断言函数(适用于非 fatal 场景),例如:
template<typename T, typename U>
bool expect_eq(const T& a, const U& b, const char* msg = "",
std::source_location loc = std::source_location::current()) {
if (a == b) return true;
fprintf(stderr, "EXPECT_EQ failed at %s:%d — %s: %s != %s\n",
loc.file_name(), loc.line(), msg,
std::to_string(a).c_str(), std::to_string(b).c_str());
return false;
}然后在测试中写:expect_eq(result, 42, "parse result");
- 注意:它不能替代
ASSERT_*的终止行为,仅适合“继续执行”的检查场景 - 若需 fatal 效果,可抛出带
loc信息的自定义异常,再由测试框架捕获并格式化 - 对浮点数、自定义类型等,要确保
==重载可用,否则编译失败
CI 环境下 source_location 输出路径太长,怎么裁剪成相对路径?
loc.file_name() 返回的是绝对路径(如 /home/ci/project/tests/test_math.cpp),日志刷屏还难定位。不能靠 string find + substr 硬切——不同 CI 工作目录结构不一致,容易切错。
稳妥做法是在编译时通过预处理器注入项目根路径,再运行时做一次前缀剥离:
#define PROJECT_ROOT "/home/ci/project"
// …
std::string_view short_path(std::string_view full, std::source_location loc) {
auto fname = loc.file_name();
auto root = std::string_view(PROJECT_ROOT);
return std::string_view(fname).substr(root.length() + 1); // 跳过根+slash
}配合构建系统(CMake)自动填入:add_compile_definitions(PROJECT_ROOT="${CMAKE_SOURCE_DIR}")
- Windows 下注意路径分隔符,
std::source_location返回的仍是正斜杠(/),但PROJECT_ROOT若含反斜杠需统一替换 - 本地开发和 CI 使用不同根路径?那就得两个定义,或改用环境变量 +
getenv(但会损失编译期优化) - 某些静态分析工具可能报“未检查 getenv 返回值”,此时加个空指针判断即可
std::source_location 会影响测试性能吗?
几乎不。它不是运行时反射,而是编译器在生成代码时把字面量(文件名字符串地址、整型行号)直接塞进指令流。调用 current() 等价于读几个寄存器或内存字,开销远小于一次 printf 或字符串拷贝。
但要注意间接成本:
- 如果日志函数频繁拼接
std::string再输出(尤其在循环里),瓶颈在内存分配,不在source_location - 开启
-g时,文件名字符串会进 debug section,不影响运行时体积;但若用-frecord-gcc-switches等特殊调试选项,可能略微增大目标文件 - 模板实例化本身不因
source_location增多——它是非模板参数,不会导致函数爆炸
真正要盯的是日志 IO 本身。把 printf 换成无锁环形缓冲 + 异步刷盘,比纠结 source_location 开销有意义得多。
别为了“自动”把日志塞进每个函数调用栈底层——该手动传就手动传,该关日志就关。source_location 是锦上添花,不是银弹。它只解决「位置信息从哪来」,不解决「要不要记」和「记多少」。










