std::source_location 必须作为带默认值的函数参数使用,且默认值只能是 std::source_location::current(),由编译器在每次调用时注入;不可保存为成员变量或全局变量,否则位置信息固定不变。

std::source_location 在 C++20 中怎么用才不报错
它不是宏,不能直接传参给函数调用;必须在调用点“现场生成”。常见错误是试图保存 std::source_location::current() 到类成员或全局变量里,结果所有日志都显示同一个位置——因为那是在定义处(比如构造函数里)取的,不是调用处。
正确做法是把 std::source_location 作为函数参数,默认值由编译器在每次调用时注入:
void log(const char* msg, std::source_location loc = std::source_location::current()) {
printf("[%s:%d] %s
", loc.file_name(), loc.line(), msg);
}- 必须用默认参数,且默认值只能是
std::source_location::current() - 不能用
auto推导这个参数类型,得写全名 - GCC 10+、Clang 11+、MSVC 19.29+ 才支持;老版本会编译失败,别硬套
为什么 printf/
这是编译器行为:clang 和 GCC 默认填入完整路径,MSVC 同样。但日志里不需要 /home/user/project/src/logger.cpp,要的是 src/logger.cpp 或更短的 logger.cpp。
别在日志函数里硬切字符串——std::source_location::file_name() 返回的是 const char*,没提供修改接口,也不建议自己做 strrchr 截取。稳妥做法是封装一层提取逻辑:
立即学习“C++免费学习笔记(深入)”;
const char* short_file_name(const char* full) {
const char* last_slash = strrchr(full, '/');
return last_slash ? last_slash + 1 : full;
}- Windows 下还要处理
'\',所以最好用std::filesystem::path(full).filename().c_str()(C++17 起),但注意这会引入额外开销 - 如果只跑 Linux/macOS,
strrchr最轻量;加个#ifdef _WIN32分支也行 - 别在日志高频路径里用
std::string构造,避免隐式内存分配
std::source_location 会影响性能吗
几乎不。它本质是几个 constexpr 整数和字符串字面量地址,编译期就确定了,运行时只是读几个字段。但有三个实际影响点容易被忽略:
- 每个带默认
std::source_location参数的函数,编译器会在调用点插入 4–6 字节的常量数据(文件名地址、行号、列号等),函数体积微增 - 如果函数内联失败(比如跨编译单元、有复杂模板推导),这些常量会多一份拷贝
- 调试信息体积会变大——因为编译器得保留更多源码映射,对最终二进制 size 影响很小,但符号表会膨胀
真正要注意的是:别把它塞进 hot path 的 inline 函数里,比如一个每帧调用几千次的向量运算辅助函数。不是性能瓶颈,但属于无谓的“语义污染”。
和传统 __FILE__/__LINE__ 宏比,到底值不值得迁
值得,但只在新项目或明确要 C++20 的模块里换。迁移不是简单替换,关键差异在语义和可控性:
-
__FILE__是宏,预处理阶段展开,无法重载、无法作为参数传递;std::source_location是类型,可存、可传、可定制输出格式 - 宏无法获取函数名,而
loc.function_name()在 GCC/Clang 下能拿到(MSVC 目前返回空字符串) - 宏拼接字符串易出错:
"[" __FILE__ ":" STRINGIFY(__LINE__) "]",而std::source_location天然结构化,字段可单独使用 - 宏无法禁用——你加了就永远存在;而
std::source_location参数可以设成可选,上线时用空实现或编译宏控制是否注入
最现实的坑是:团队里有人顺手把 std::source_location::current() 写在函数体第一行,以为能代表入口位置——其实它代表那一行代码的位置,不是函数起点。真要统一入口,还是得靠参数默认值机制。










