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

能否仅验证一次std::vformat格式字符串以重复复用?

在C++20中复用预解析的格式字符串以避免重复开销

C++20标准库的std::vformat确实会在每次调用时重新解析格式字符串,没有直接提供预解析并复用的公共API,但可以通过以下几种方案实现需求:

方案1:手动解析并缓存格式信息(C++20兼容)

针对运行时格式字符串,我们可以手动解析出字面量片段和每个占位符的格式规范,缓存这些结果后,后续格式化时直接复用,避免重复解析整个字符串。示例代码如下:

#include <format>
#include <string>
#include <vector>
#include <utility>

// 缓存解析后的格式内容:字面量前缀、各占位符的格式规范、字面量后缀
struct ParsedFormat {
    std::string literal_prefix;
    std::vector<std::string> format_specs;
    std::string literal_suffix;
};

// 解析格式字符串,提取关键信息
ParsedFormat parse_format(const std::string& fmt) {
    ParsedFormat result;
    std::size_t pos = 0;
    const std::size_t fmt_len = fmt.size();

    while (pos < fmt_len) {
        // 定位下一个占位符起始
        const auto open_brace = fmt.find('{', pos);
        if (open_brace == std::string::npos) {
            result.literal_suffix += fmt.substr(pos);
            break;
        }

        // 添加占位符前的字面量
        result.literal_prefix += fmt.substr(pos, open_brace - pos);
        pos = open_brace + 1;

        // 定位占位符结束
        const auto close_brace = fmt.find('}', pos);
        if (close_brace == std::string::npos) {
            throw std::format_error("Unclosed format specifier");
        }

        // 提取占位符内的格式规范(如:.2f、:x等)
        result.format_specs.push_back(fmt.substr(pos, close_brace - pos));
        pos = close_brace + 1;
    }

    return result;
}

// 使用预解析的格式信息进行格式化
template<typename... Args>
std::string format_with_parsed(const ParsedFormat& parsed, Args&&... args) {
    std::string result = parsed.literal_prefix;
    std::size_t arg_idx = 0;

    // 逐个处理参数并格式化
    auto process_arg = [&](const auto& arg) {
        if (arg_idx >= parsed.format_specs.size()) {
            throw std::format_error("Too many arguments provided");
        }

        const auto& spec = parsed.format_specs[arg_idx];
        using Formatter = std::formatter<std::decay_t<decltype(arg)>>;
        
        Formatter fmt;
        std::format_parse_context ctx(spec);
        fmt.parse(ctx); // 复用预提取的格式规范

        std::format_to(std::back_inserter(result), fmt, arg);
        arg_idx++;
    };

    (process_arg(std::forward<Args>(args)), ...);
    result += parsed.literal_suffix;
    return result;
}

int main() {
    std::string format_str = "Value: {} (hex: {:x}, fixed: {:.2f})";
    // 仅解析一次格式字符串
    const auto parsed_format = parse_format(format_str);

    // 多次复用解析结果
    std::string res1 = format_with_parsed(parsed_format, 42, 42, 42.0);
    std::string res2 = format_with_parsed(parsed_format, 43, 43, 43.0);

    return 0;
}

方案2:升级到C++23利用扩展API

C++23对格式化库进行了扩展,允许更灵活地操作解析上下文。你可以直接使用std::format_parse_context预解析格式字符串,将解析后的std::formatter实例缓存起来,后续直接调用其格式化方法,无需重复解析整个格式串。

方案3:使用第三方库(如{fmt})

作为C++标准格式化库的前身,{fmt}库提供了fmt::compile_format接口,可预编译运行时格式字符串,生成可复用的编译后格式对象,性能优于手动解析:

#include <fmt/core.h>

int main() {
    std::string format_str = "Count: {}";
    // 预编译格式字符串(运行时验证格式合法性)
    auto compiled_fmt = fmt::compile_format<fmt::format_string<int>>(format_str);

    // 多次复用编译后的格式
    std::string res1 = fmt::format(compiled_fmt, 42);
    std::string res2 = fmt::format(compiled_fmt, 43);

    return 0;
}

方案4:针对简单格式的简易缓存

如果你的格式字符串仅包含无修饰的{}占位符,可直接拆分字符串为字面量片段,每次格式化时替换占位符,实现成本最低:

#include <string>
#include <sstream>
#include <tuple>
#include <variant>

struct SimpleSplitFormat {
    std::vector<std::string> segments; // 交替存储字面量和空字符串(占位符标记)
};

SimpleSplitFormat split_simple_format(const std::string& fmt) {
    SimpleSplitFormat result;
    std::size_t pos = 0;
    const std::size_t fmt_len = fmt.size();

    while (pos < fmt_len) {
        const auto open_brace = fmt.find('{', pos);
        if (open_brace == std::string::npos) {
            result.segments.push_back(fmt.substr(pos));
            break;
        }

        result.segments.push_back(fmt.substr(pos, open_brace - pos));
        // 验证是否为无修饰占位符
        if (open_brace + 1 >= fmt_len || fmt[open_brace + 1] != '}') {
            throw std::format_error("Only simple {} placeholders are supported");
        }
        result.segments.push_back(""); // 用空字符串标记占位符位置
        pos = open_brace + 2;
    }

    return result;
}

template<typename... Args>
std::string format_simple(const SimpleSplitFormat& fmt, Args&&... args) {
    std::ostringstream oss;
    std::size_t arg_idx = 0;
    const auto args_tuple = std::make_tuple(std::forward<Args>(args)...);

    for (const auto& seg : fmt.segments) {
        if (!seg.empty()) {
            oss << seg;
        } else {
            if (arg_idx >= sizeof...(Args)) {
                throw std::format_error("Insufficient arguments");
            }
            std::visit([&](const auto& arg) { oss << arg; }, std::get<arg_idx>(args_tuple));
            arg_idx++;
        }
    }

    return oss.str();
}

int main() {
    std::string format_str = "Hello {}, your score is {}";
    const auto split_fmt = split_simple_format(format_str);

    std::string res1 = format_simple(split_fmt, "Alice", 95);
    std::string res2 = format_simple(split_fmt, "Bob", 88);

    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 06:24:57