You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

存储std::format_args时的生命周期问题及替代方案咨询

问题:std::format_args生命周期导致异常类what()失效

请查看以下代码:

#include <print>
#include <exception>
#include <string>
#include <format>

class exception_t
    : public std::exception
{
public:
    template<class... args_t>
    exception_t(std::string_view users_fmt, args_t&&... args)
        : m_users_fmt(users_fmt)
        , m_format_args(std::move(std::make_format_args(args...)))
    {}
    char const* what() const noexcept override
    {
        thread_local static std::string s = std::vformat(m_users_fmt, m_format_args);
        return s.c_str();
    }
private:
    std::string m_users_fmt;
    std::format_args m_format_args;
};

int main()
try
{
    throw exception_t("{}", 42);
}
catch (std::exception& e)
{
    std::println("{}", e.what());
}

在MSVC 2022 v143搭配SDK 10.0.19041.0环境下调试这段代码时,构造exception_t期间m_format_args拥有有效值,但调用what()时m_format_args的值已失效,程序通常无法输出42,请问哪里出错了?


更新需求

以上是最小化代码片段,初衷是将格式化操作与异常/错误/警告报告完全分离。希望将参数以标准类型存储(例如存入磁盘),之后由另一个进程读取并使用本地化语言的格式字符串展示数据,因此不想在构造函数中调用std::vformat。是否存在std::format_args的替代方案?例如,能否直接存储参数?


解答

原问题错误原因

你的问题出在std::format_args的生命周期依赖上。std::format_args是一个轻量级的类型擦除视图,它并不存储传入的参数本身,仅持有对这些参数的引用。

在exception_t的构造函数中,std::make_format_args(args...)生成的format_args对象,完全依赖于构造函数参数中转发的args...的生命周期。当构造函数执行完毕后,这些args(比如你传入的字面量42这类临时对象)会被销毁,导致m_format_args引用的内存变为无效的垃圾数据。后续调用what()时使用这个失效的format_args属于未定义行为,因此无法正确输出结果。

适配需求的替代方案

如果要实现格式化与异常报告分离,且需要持久化参数供跨进程使用,std::format_args因仅为视图类型完全不适用,以下是几种可行方案:

  1. 使用std::dynamic_format_arg_store(C++20+)
    C++20提供的std::dynamic_format_arg_store可以存储格式化参数的副本,而非引用,其生命周期与对象本身一致。修改后的示例代码如下:

    #include <print>
    #include <exception>
    #include <string>
    #include <format>
    
    class exception_t
        : public std::exception
    {
    public:
        template<class... args_t>
        exception_t(std::string_view users_fmt, args_t&&... args)
            : m_users_fmt(users_fmt)
            , m_args(std::make_format_args_store<std::format_context>(std::forward<args_t>(args)...))
        {}
        char const* what() const noexcept override
        {
            thread_local static std::string s;
            try {
                s = std::vformat(m_users_fmt, std::make_format_args(m_args));
            } catch(...) {
                s = "Format error";
            }
            return s.c_str();
        }
    private:
        std::string m_users_fmt;
        std::dynamic_format_arg_store<std::format_context> m_args;
    };
    
    int main()
    try
    {
        throw exception_t("{}", 42);
    }
    catch (std::exception& e)
    {
        std::println("{}", e.what());
    }
    

    该方案完全适配std::format生态,但std::dynamic_format_arg_store无法直接序列化,若需持久化到磁盘,需额外实现序列化逻辑。

  2. 存储可序列化的参数容器
    将参数存储为std::vector<std::any>或自定义的std::variant(包含所有可能的参数类型),同时记录参数类型信息。需要格式化时,将std::any转换回原类型再传递给std::format。此方式需自行处理类型转换与序列化,但优势是可轻松持久化,供其他进程读取。

  3. 预序列化为通用格式
    将参数直接序列化为JSON、XML等通用格式存储,其他进程读取后可自行解析并格式化。该方案最适合跨进程、跨语言场景,但需引入序列化库(如nlohmann/json)。


内容的提问来源于stack exchange,提问作者Jeroen Lammertink

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 13:29:50