std::source_location不能直接放进异常里,必须在throw时通过std::source_location::current()当场捕获调用点信息并拼入错误字符串,或封装为宏确保位置准确;自定义异常类存储source_location易引发noexcept违规和兼容性问题。

std::source_location 是什么,它能直接放进异常里吗
std::source_location 是 C++20 引入的轻量结构体,用于在编译期捕获文件名、函数名、行号和列号。但它本身不参与运行时异常传播——不能直接作为 std::exception 的成员被抛出,因为它的构造依赖于隐式编译器注入(如 std::source_location::current()),而异常对象通常需要可复制、可移动、无抛出构造,且 std::source_location 的默认构造函数是 constexpr 但“空值”状态(如空字符串)无意义。
所以你不能这么写:
throw std::runtime_error("failed"); // ❌ 没有位置信息
也不能这样塞进去:
throw MyException("oops", std::source_location::current()); // ❌ 若 MyException 构造函数非 noexcept 或含非 trivial 成员,可能中途抛异常
如何安全地把调用点信息附加到异常消息中
最常用也最稳妥的做法:在 throw 表达式所在位置,当场调用 std::source_location::current(),拼接进错误字符串。关键在于「立即求值」+「字符串化」,避免延迟或跨作用域传递。
-
std::source_location::current()必须出现在 throw 语句的同一行(或宏展开后实际生成的位置),否则记录的是宏/包装函数的调用点,不是原始调用点 - 推荐封装成一个内联宏或 constexpr 函数,但宏更可靠(C++20 中仍难用函数完美替代)
- 注意
file_name()返回的是 const char*,不是std::string,拼接前需转为std::string_view或用std::string构造
示例(推荐宏方式):
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
#define THROW_RUNTIME_ERROR(msg) \
do { \
auto loc = std::source_location::current(); \
std::string full_msg = std::string(msg) + " [" + \
std::string(loc.file_name()) + ":" + \
std::to_string(loc.line()) + "]"; \
throw std::runtime_error(full_msg); \
} while(0)
// 使用
void risky_func() {
if (bad_thing_happens) {
THROW_RUNTIME_ERROR("network timeout"); // ✅ 记录的是这一行
}
}
为什么不用自定义异常类存 source_location
看似合理:定义一个带 std::source_location 成员的异常类,重载 what() 动态拼接。但实际容易踩坑:
-
std::source_location对象本身不含动态内存,但若你在what()里每次拼接都 new 字符串或调用std::to_string,可能抛异常(比如分配失败),违反what()的noexcept要求 - 如果异常对象在栈上构造后又被移动/复制,
std::source_location成员没问题,但配套的缓存字符串(如m_msg)若未正确实现 move semantics,可能悬垂或重复析构 - 多数日志或调试器只读
what()返回的 C 字符串,不解析异常类型,所以额外字段基本浪费
结论:除非你有强需求(如统一异常处理框架需提取结构化位置字段),否则纯字符串拼接更简单、更兼容、更安全。
MSVC / GCC / Clang 在 source_location 上的行为差异
三者都支持 C++20 std::source_location,但细节有别:
- Clang 和 GCC 从 10+/12+ 开始支持,
file_name()返回绝对路径(取决于编译命令中的-I或工作目录),可能很长;可用std::filesystem::path(loc.file_name()).filename().string()截取,但注意这会引入异常风险(建议仅调试时用) - MSVC(VS 2019 16.10+)默认返回相对路径(相对于项目根),更简洁;但若用 Ninja 生成器,行为可能退化为绝对路径
- 所有编译器中,
function_name()都不保证是“用户友好的”,GCC/Clang 返回 mangled 名(如_Z8foo_intv),MSVC 返回类似?foo@A@@SAHXZ;别指望靠它做可读函数名,老实用__func__或硬编码
所以生产环境建议只拼接 file_name() 和 line(),放弃 function_name() —— 它既不可靠,又不跨平台,还增加开销。
真正难的不是怎么记下位置,而是确保每处 throw 都经过同一套机制;漏掉一个裸 throw,日志就断一节。宏不是银弹,但它是目前最可控的落地方式。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










