std::source_location在跨平台异步日志中必须在宏中即时捕获,否则会记录日志器内部位置;应封装为POD结构体(file/func/line)随消息入队,避免传参或延迟获取。

std::source_location 在跨平台异步日志中不能直接用,必须在日志调用点即时捕获,否则拿到的是日志器内部的行号和函数名——这是绝大多数人踩的第一个坑。
为什么 std::source_location 传参后会失效
因为 std::source_location::current() 是编译期常量表达式,它在调用点展开为当前源码位置。一旦你把它作为参数传给异步日志函数(比如塞进队列),这个值就固定了;但若该函数本身是内联或被优化,或者你在封装层(如 LOG_INFO 宏)里只调用一次 current(),后续所有日志都会显示宏定义所在行,而非用户真实调用行。
- 错误写法:
void async_log(const char* msg, std::source_location loc = std::source_location::current())→ 如果这个函数被内联或跨编译单元调用,loc值可能来自日志库实现文件 - 正确做法:必须在最外层宏里立即捕获,且禁止透传
std::source_location参数到异步处理线程 - 关键约束:C++20 要求
std::source_location::current()必须出现在“调用上下文”,不能出现在 lambda 捕获、成员函数默认参数、或模板推导中
如何安全地在宏中捕获并传递位置信息
宏是唯一能保证 std::source_location::current() 展开在用户代码行的机制。你需要把位置信息序列化为轻量结构体(如 struct LogLoc { int line; const char* file; const char* func; }),再随消息一起入队。
- 推荐宏定义:
#define LOG_INFO(msg) do { ::log_async("INFO", msg, std::source_location::current()); } while(0) - 注意:不要用
constexpr函数包装current(),编译器可能将它提前求值 - Windows MSVC 和 GCC/Clang 对
__func__的行为一致,但std::source_location::function_name()返回的是完整签名(含返回类型和参数),实际日志中建议截断或用__func__替代 - 跨平台兼容性:Clang 12+、GCC 10+、MSVC 19.29+ 支持,旧版本需 fallback 到
__FILE__/__LINE__/__func__
异步队列里怎么存 location 数据才不拖慢性能
不能直接存 std::source_location 对象——它内部有四个 const char* 成员,但这些字符串字面量生命周期与程序相同,所以可以只存指针 + 整数,总大小控制在 16 字节以内。
立即学习“C++免费学习笔记(深入)”;
- 安全做法:
struct LogLoc { int line; const char* file; const char* func; };—— 所有字段都是 POD,可 memcpy,无构造/析构开销 - 避免 string 拷贝:不要调用
std::source_location::file_name().c_str(),直接用__FILE__;同理,用__func__替代function_name()防止 ABI 差异 - 实测影响:在 100 万次/秒日志压测下,额外 12 字节元数据对吞吐影响
真正难的不是获取位置,而是确保它从宏展开那一刻起,就不再经过任何函数调用栈或对象生命周期管理——只要中间多一层间接,就大概率丢失原始上下文。跨平台异步日志里,宁可用三套预处理器分支(#ifdef _WIN32 / #ifdef __GNUC__),也不要依赖某个编译器对 std::source_location 的非标准扩展行为。


















