能否仅验证一次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
相关产品推荐
相关产品推荐

