C++20用std::source_location最可靠;老标准依赖__func__等预定义宏,但其为编译器注入的静态字符数组,不可取地址、不可传模板、宏中展开返回定义处而非调用处函数名;std::source_location需显式调用::current()获取调用点信息,否则为无效值。

直接说结论:C++20 用 std::source_location 最可靠;老标准只能靠预定义宏(__func__、__FILE__、__LINE__),但它们不是函数调用,不能传参或延迟求值,容易误用。
为什么 __func__ 不是标准函数,也不能取地址
__func__ 是编译器在每个函数作用域内自动注入的静态字符数组(如 static const char __func__[] = "foo";),它不是变量声明,也不参与表达式求值时机控制。
- 不能写
&__func__—— 编译报错:address of function '__func__' cannot be taken - 不能作为参数直接传给模板函数(除非模板接受
const char*且实参是字面量上下文) - 在宏里展开时,它始终返回宏定义处的函数名,而非调用处的 —— 这是最大陷阱
std::source_location 怎么用才不掉坑
它是 C++20 引入的轻量结构体,通过编译器内置支持生成调用点信息(函数名、文件、行号、列号),关键在于:它默认构造时捕获的是 调用点,不是定义点。
- 必须显式传递,默认不会自动注入:写
log("msg", std::source_location::current()),别漏掉::current() - 不能写成
std::source_location loc;—— 这会初始化为无效值(line() == 0,function_name()为空) - 性能几乎无开销:编译期填入常量,不涉及 runtime 调用或字符串拷贝
- 注意 MSVC 2019 v16.8+ 和 GCC 10+ / Clang 11+ 才完整支持;低版本可能只返回空字符串或 0 行
void log(const char* msg, const std::source_location& loc = std::source_location::current()) {
std::cout << loc.file_name() << ":" << loc.line()
<< " [" << loc.function_name() << "] " << msg << "\n";
}
// 调用处自动捕获当前行和函数名
log("start processing"); // 输出:main.cpp:42 [main] start processing
预定义宏在日志宏里的典型误用与修复
很多人写日志宏时直接拼接 __FILE__ 和 __LINE__,却忽略了宏展开时机问题 —— 它们总在宏体内部展开,而非调用位置。
立即学习“C++免费学习笔记(深入)”;
- 错误写法:
#define LOG(x) std::cerr → 总显示宏定义所在行 - 正确写法:用
__FILE__和__LINE__作为宏参数透传,或改用std::source_location - 若必须兼容 C++17,可这样修:
#define LOG(x) do { ::log_impl(x, __FILE__, __LINE__, __func__); } while(0) -
__FUNCTION__和__PRETTY_FUNCTION__是 GCC/Clang 扩展,前者等价__func__,后者含签名(如void foo(int)),但不可移植
真正麻烦的从来不是“怎么拿到名字”,而是“拿到的是哪一层的名字”——宏、内联、模板实例化、lambda 都会让 __func__ 或 source_location 指向意料之外的位置。调试时多打几行 std::source_location::current() 对比,比查文档更快定位问题。

















