c++oding="utf-8" ?>
std::source_location 不会自动出现在 throw 处,因为它是纯值类型、无构造副作用,且标准要求仅在调用 current() 时生成位置信息;编译器不会在 throw 前隐式插入该调用。

std::source_location 不能自动追踪异常抛出点——它不参与异常传播,也不在 throw 时隐式捕获位置信息;你必须显式调用 std::source_location::current() 并将其作为参数传入异常对象或日志逻辑中。
为什么 std::source_location 不会自动出现在 throw 处
std::source_location 是一个纯值类型,没有构造函数副作用,也不会被编译器在 throw 语句中自动注入。C++ 标准明确要求:它的值只在调用 current() 的那一行代码处生成,且仅反映该次调用的上下文。
常见误解是把它类比为 Python 的 traceback 或 Java 的 fillInStackTrace(),但 C++ 没有运行时栈帧自动采集机制。
- 编译器不会在
throw e;前悄悄插一句auto loc = std::source_location::current(); - 即使你在
catch块里调用std::source_location::current(),得到的也是catch所在行,不是throw行 - 所有主流编译器(GCC 12+、Clang 14+、MSVC 19.3x+)都严格遵循这一语义
如何手动把 source_location 带进异常对象
最直接的方式是让自定义异常类持有 std::source_location 成员,并在抛出时显式传入。标准异常如 std::runtime_error 不支持这个能力,必须封装或继承。
示例:
struct tracked_exception : std::runtime_error {
std::source_location loc;
tracked_exception(const std::string& msg,
std::source_location l = std::source_location::current())
: std::runtime_error(msg), loc(l) {}
};
// 使用
throw tracked_exception("buffer overflow", std::source_location::current());
- 不要省略默认参数的
std::source_location::current()调用——否则传进去的是零初始化值(文件名为空、行号为 0) - 如果异常类型是第三方库提供的(如
boost::exception),需通过其提供的机制(如boost::enable_error_info)注入位置信息 - 避免在模板函数里直接用
std::source_location::current()作为默认参数,某些旧版 Clang 可能因模板实例化时机导致位置偏移
配合日志系统做轻量级抛出点记录
比起改造所有异常类型,更实用的做法是在关键 throw 前统一打日志,利用宏隐藏重复代码。
示例宏定义:
#define THROW_AT(msg) \
do { \
auto __loc = std::source_location::current(); \
log_error("THROW at {}:{}:{}", __loc.file_name(), __loc.line(), __loc.function_name()); \
throw std::runtime_error(msg); \
} while(0)
// 使用
if (size > capacity) {
THROW_AT("capacity exceeded");
}
- 宏中必须用
do { ... } while(0)包裹,否则在if后无花括号时会出错 - 不要试图在宏里构造带
source_location的异常对象再 throw——这会让调试器看到的是宏展开后的 throw 行,而非原始调用行 - 若项目已用
spdlog或glog,可复用其内置的__FILE__/__LINE__宏,但注意std::source_location能提供更准确的函数名(含重载/模板签名),而传统宏不能
调试时容易忽略的兼容性细节
即便代码写对了,std::source_location 在不同环境下的行为仍有差异,尤其影响调试体验。
- Clang 14–15 在启用
-O2且未加-g时,file_name()可能返回空字符串;GCC 12 默认保留,但建议始终加-g - MSVC 19.32+ 支持完整字段,但
function_name()在模板函数内可能只显示实例化名(如"foo<int>"</int>),而非声明处的原始签名 - 静态链接 libc++ 时,某些嵌入式交叉编译链(如 aarch64-linux-gnu-g++)尚未完全实现
source_location,调用current()可能返回全零值——务必在目标平台实测
真正麻烦的不是怎么写,而是你以为它“自动生效”了,结果线上崩溃日志里全是 unknown:0。每次加新模块前,先写个最小测试用例跑一遍 source_location::current() 输出,比事后查三天堆栈强得多。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











