std::source_location::current() 总是返回封装函数内部行号,因其在函数体内调用时绑定该处位置;必须将其作为默认参数(void log_info(const char* msg, const std::source_location& loc = std::source_location::current()))并确保用户直接调用,避免任何中间层转发。

在自动化构建系统(如 Bazel、CMake + Ninja)中用 std::source_location 追踪日志行号,必须确保它出现在最终用户调用点,否则所有日志都会固定显示封装函数内部的行号(比如 build.cpp:15)。这不是编译器 bug,而是调用链层级被截断的必然结果。
为什么构建系统里 std::source_location::current() 总是返回同一行
根本原因是:你在 BuildLogger::write() 或类似封装函数里调用了 std::source_location::current(),它就永远绑定到那个函数体内的位置。构建脚本调用的是 logger.info("linking..."),但中间经过了至少一层转发——这一层就“吃掉”了原始调用点。
- 模板函数、lambda、
std::function回调、甚至带转发语义的包装器,都会让loc指向它们自己的定义行,而非用户CMakeLists.txt或BUILD文件附近的真实代码行 - 即使启用了 LTO 或跨 TU 内联,只要函数签名没显式声明
std::source_location默认参数,编译器就不会在调用处注入位置信息 - CI 环境(Linux runner / macOS self-hosted / Windows Azure Pipelines)不会改变该行为,差异只体现在
file_name()返回的路径格式上
如何让日志真正指向用户源码行(非封装层)
唯一可靠路径是:把 std::source_location 作为默认参数暴露给最终调用者,并禁止任何中间层透传或缓存。
- 日志函数签名必须为:
void log_info(const char* msg, const std::source_location& loc = std::source_location::current()),且loc必须是最后一个参数 - 禁止写
auto cached_loc = std::source_location::current()并复用;也禁止把它存成类成员或全局变量——它一旦构造,位置就固化了 - 若需支持格式化(如
LOG("x = %d", x)),必须用宏展开:#define LOG(fmt, ...) ::detail::log_impl(fmt, ##__VA_ARGS__, std::source_location::current()),否则__VA_ARGS__会破坏参数顺序 - 不要在宏里做
std::string构造或路径裁剪——高频构建日志下,隐式分配和重复计算是性能瓶颈
file_name() 路径太长且跨平台不一致怎么办
std::source_location::file_name() 返回的是完整绝对路径(如 /home/ci/.cache/bazel/_bazel_ci/abc123/execroot/proj/src/main.cpp),直接打日志既冗余又暴露 CI 路径结构。裁剪必须发生在调用点,且不能依赖 std::filesystem(嵌入式或精简工具链可能不支持)。
立即学习“C++免费学习笔记(深入)”;
- 安全裁剪方式是用
std::string_view手动找最后一个/或:std::string_view f(loc.file_name()); auto last_slash = f.find_last_of("/\"); if (last_slash != std::string_view::npos) f.remove_prefix(last_slash + 1); - 不要用
std::filesystem::path(f).filename().string()——它可能触发堆分配,且在无std::filesystem的构建环境(如某些交叉编译链)会编译失败 - Windows 下注意
file_name()可能含反斜杠,find_last_of("/\")是唯一可移植写法 - CI 构建节点上路径前缀千差万别,裁剪逻辑必须在日志函数体内完成,不能提前缓存或预处理
最易被忽略的一点:你无法靠 std::source_location 还原出用户 CMakeLists.txt 行号或 BUILD 文件位置——它只对 C++ 源码有效。如果构建系统本身是用 Python 或 Starlark 写的,那得靠对应语言的调试设施,std::source_location 在那里完全不生效。


















