Folly Format 指南:folly::format 类型安全格式化设施的语法、实现与扩展
2026/9/10 23:56:24 网站建设 项目流程

Folly Format 指南:folly::format 类型安全格式化设施的语法、实现与扩展

【免费下载链接】follyAn open-source C++ library developed and used at Facebook.项目地址: https://gitcode.com/GitHub_Trending/fol/folly

folly::format是 Facebook(Meta)开源的 C++ 库 folly 提供的文本格式化设施,其规范语言与 Python 的str.format类似,用于将字符串、整数、浮点数以及动态类型的folly::dynamic对象安全地组装成可读文本。本文以仓库中的官方文档 folly/docs/Format.md 为主体,结合 folly/Format.h、folly/FormatArg.h、folly/Format-inl.h 等源码实现,完整讲解格式串语法、格式规范、容器取值、错误处理与自定义类型扩展,帮助读者在项目里直接落地这套类型安全的格式化方案。

快速上手

folly/Format.h提供了一个快速、强大、类型安全且灵活的文本格式化设施,默认即可格式化字符串、整数、浮点数以及动态类型的folly::dynamic对象,并能从随机访问容器与字符串键映射中取值。在许多场景下,formatsprintf更快,并且完全类型安全。以下示例直接取自官方文档,可快速入门:

using folly::format; using folly::sformat; // format() 产生的对象可直接流入流中,无需创建中间字符串; // {} 按默认格式取下一个参数。 std::cout << format("The answers are {} and {}", 23, 42); // => "The answers are 23 and 42" // 如果只需要字符串,直接使用 sformat。 std::string result = sformat("The answers are {} and {}", 23, 42); // => "The answers are 23 and 42" // 要输出字面量 '{' 或 '}',将其加倍即可。 std::cout << format("{} {{}} {{{}}}", 23, 42); // => "23 {} {42}" // 参数可以不按顺序引用,甚至可以多次引用。 std::cout << format("The answers are {1}, {0}, and {1} again", 23, 42); // => "The answers are 42, 23, and 42 again" // 不引用全部参数也完全没问题。 std::cout << format("The only answer is {1}", 23, 42); // => "The only answer is 42" // 可以从可索引容器(随机访问序列、整数键映射)以及字符串键映射中取值。 std::vector<int> v {23, 42}; std::map<std::string, std::string> m { {"what", "answer"} }; std::cout << format("The only {1[what]} is {0[1]}", v, m); // => "The only answer is 42" // format 支持 pair 与 tuple。 std::tuple<int, std::string, int> t {42, "hello", 23}; std::cout << format("{0} {2} {1}", t); // => "42 23 hello" // 支持宽度、对齐、任意填充字符以及各种格式说明符,语义与 printf 类似。 // "X<10":以 'X' 填充、左对齐('<')、宽度 10。 std::cout << format("{:X<10} {}", "hello", "world"); // => "helloXXXXX world" // 字段宽度可以是运行时值,而不必写在格式串里。 int x = 6; std::cout << format("{:-^*}", x, "hi"); // => "--hi--" // 显式参数配合动态宽度使用时,值参数与宽度参数都必须给出索引。 std::cout << format("{2:+^*0}", 9, "unused", 456); // => "+++456+++" // 支持 printf 风格格式说明符。 std::cout << format("{0:05d} decimal = {0:04x} hex", 42); // => "00042 decimal = 002a hex" // Formatter 对象可借助 folly::to / folly::toAppend(见 folly/Conv.h) // 写入字符串,也可调用 appendTo() 与 str() 方法。 std::string s = format("The only answer is {}", 42).str(); std::cout << s; // => "The only answer is 42" // 小数精度用法。 std::cout << format("Only 2 decimals is {:.2f}", 23.34134534535); // => "Only 2 decimals is 23.34"

格式串语法

格式串format的完整文法如下:

"{" [arg_index] ["[" key "]"] [":" format_spec] "}"

各组成部分说明:

  • arg_index:待格式化参数的索引;默认取下一个参数。注意:一份格式串要么全部使用默认参数索引,要么全部使用显式索引,二者不能混用(以免引起歧义)。源码中的校验见 folly/test/FormatTest.cpp,例如sformat("{0} {} {1}", 0, 1, 2)会抛出BadFormatArg,错误信息为"may not have both default and explicit arg indexes"
  • key:当参数是容器(C 风格数组或指针、std::array、vector、deque、map)时,用key选取要格式化的元素;支持随机访问序列、整数键映射与字符串键映射。多级 key 也支持,各层之间用.分隔;例如对map<string, map<string, string>> m{[foo.bar]}会选择m["foo"]["bar"]。参数索引越界或 key 不存在时抛出异常,详见后文"错误处理"一节。
  • format_spec:格式规范,见下一节。

格式规范

格式规范format_spec的文法如下:

[[fill] align] [sign] ["#"] ["0"] [width] [","] ["." precision] ["."] [type]

各组成部分逐项说明(均取自官方文档):

  • fill(仅当同时指定align时才允许):用于填充的字符,' '(空格)与'0'(零)较为常用;默认填充字符是空格。
  • align:对齐方式,取值为'<''>''=''^'之一:
    • '<':左对齐(大多数对象的默认对齐方式);
    • '>':右对齐(数字的默认对齐方式);
    • '=':在符号之后、有效数字之前填充,用于打印如-0000120这种形式;仅对数字有效;
    • '^':居中对齐。
  • sign:符号控制,取值为'+''-'、空格(仅对数字有效):
    • '+':正数或零时输出'+',负数时输出'-'
    • '-':负数时输出'-',否则不输出(默认行为);
    • ' '(空格):正数或零时输出一个空格,负数时输出'-'
  • '#':输出进制前缀(八进制为0,二进制为0b0B,十六进制为0x0X;仅对整数有效)。
  • '0':符号之后补零,等价于把fillalign指定为"0="(仅对数字有效)。
  • width:最小字段宽度。可以是'*',表示字段宽度由某个参数给出;默认取下一个参数(即待格式化值前面的那个参数),也可以在'*'后跟显式参数索引。从源码看,FormatArgkDefaultWidth = -1表示未指定、kDynamicWidth = -2表示动态宽度(见 folly/FormatArg.h)。动态宽度相关约束同样有测试覆盖:sformat("{:*}", 1.2)"dynamic field width argument must be integral"sformat("{:*0}", 12, "ok")"cannot provide width arg index without value arg index"sformat("{0:*}", 12, "ok")"cannot provide value arg index without width arg index"
  • ','(逗号):输出逗号作为千位分隔符(仅对整数有效,且仅限十进制输出)。测试用例见 folly/test/FormatTest.cpp 的separatorDecimalIntegerseparatorNumberseparatorUnit系列。
  • precision(整数不允许使用):
    • 对浮点值:表示小数点后的位数('f''F'呈现)或有效数字个数('g''G');
    • 对其他类型:表示最大字段大小(截断后续字符)。
  • '.'(在 precision 之后使用,或代替 precision):强制输出尾部小数点,以明确这是浮点值。
  • type:呈现格式,见下一节。

呈现格式(Presentation Formats)

字符串folly::StringPiecestd::stringfolly::fbstringconst char*):

  • 's'(默认)。

整数

  • 'b':以二进制(基 2)输出(指定'#'时带0b前缀);
  • 'B':以二进制(基 2)输出(指定'#'时带0B前缀);
  • 'c':作为字符输出(强转为char);
  • 'd':以十进制(基 10)输出(默认);
  • 'o':以八进制(基 8)输出;
  • 'O':以八进制(基 8)输出(与'o'相同);
  • 'x':以十六进制(基 16)输出(大于 9 的数字用小写字母);
  • 'X':以十六进制(基 16)输出(大于 9 的数字用大写字母);
  • 'n':locale 感知输出(当前与'd'相同)。

bool

  • 默认以字符串"true""false"输出;
  • 也允许使用整数呈现格式。

char

  • 与其他整数相同,但默认呈现是'c'而非'd'

浮点floatdoublelong double未实现):

  • 'e':以e作为指数符号的科学计数法;
  • 'E':以E作为指数符号的科学计数法;
  • 'f':定点表示;
  • 'F':定点表示(与'f'相同);
  • 'g':通用表示;根据数值大小选择'f''e'(默认);
  • 'G':通用表示;根据数值大小选择'f''E'
  • 'n':locale 感知的'g'(当前与'g'相同);
  • '%':百分比:先乘以 100,再以'f'形式显示。

Formatter 的多种输出方式

format()返回的是一个Formatter对象而不是字符串,这是它区别于sprintf的关键设计。Formatter对象的头文件注释明确指出:它持有左值参数的引用(同时接管临时对象的生命周期),并且不拷贝传入的格式串,因此不能直接构造使用,必须经由format(...)入口获得(见 folly/Format.h)。

针对该对象可以:

  • 直接流入流operator<<已针对Formatter重载(见 folly/Format.h),逐段写出而无需创建中间字符串;
  • 转为字符串:调用.str()方法,或通过folly::to/folly::toAppend(见 folly/Conv.h)写入字符串;
  • 追加到已有字符串:调用.appendTo(str)str可以是std::stringfbstring等 folly 字符串类型(folly/Format.h);
  • 直接格式化进字符串指针format(&foo, "{} {}", 42, 23),这是toAppend(format(...), &foo)的快捷方式(folly/Format.h)。

如果只想要字符串结果,直接使用sformat(fmt, args...),它在内部构造Formatter后立即调用.str()返回(folly/Format.h)。

容器取值与动态对象

可索引容器与字符串键映射

格式串中的[key]语法对应FormatArg内部的键拆分逻辑:splitKey().拆分多级 key,splitIntKey()则把 key 解析为整数(folly/FormatArg.h)。容器支持通过偏特化机制分派:

  • 整数可索引容器(std::arraystd::vectorstd::deque、整数键的std::map/std::unordered_map)由IndexableTraits描述,对应的FormatValue偏特化通过splitIntKey()取元素(folly/Format-inl.h);
  • 字符串键映射(键类型为std::stringfbstringStringPiecestd::map/std::unordered_map)由KeyableTraits描述,通过splitKey()取元素(folly/Format-inl.h)。

因此文档中的format("The only {1[what]} is {0[1]}", v, m)实际走的是"先取参数 1 的what键,再取参数 0 的下标 1"两条取数链路。越界行为由测试确认:sformat("{[5]}", ints)对越界下标抛出std::out_of_rangesformat("{[nope]}", map)对不存在的键同样抛出std::out_of_range(folly/test/FormatTest.cpp)。

pair 与 tuple

FormatValuestd::pair<A, B>std::tuple<Args...>有专门偏特化(folly/Format-inl.h),因此format("{0} {2} {1}", t)这类写法才得以生效。

dynamic 对象

folly::dynamic是 folly 的动态类型值,官方文档把它列为默认可格式化对象之一。其实现位于 folly/json/dynamic-inl.h:FormatValue<dynamic>format()dynamic的实际类型分派——NULLT走空指针输出、BOOLboolINT64int64_tSTRING走字符串、DOUBLE走浮点,而ARRAY用整数 key 取元素、OBJECT用字符串 key 取元素。这使得format("{[name]}", dynamicObj)这样的写法可以直接作用在 JSON 风格的动态对象上。

扩展:为自定义类型提供 FormatValue 特化

format的可扩展性是官方文档明确支持的特性:为folly::FormatValue提供特化,即可让自定义类型参与格式化。文档建议参考folly/Format.hfolly/FormatArg.h,并以 folly/json/dynamic-inl.h 中folly::dynamic的现成特化作为实现范例。

扩展要点(见 folly/Format.h 的注释):

  • FormatValue<T>以(引用折叠后的)T&&构造,该引用保证在FormatValue对象销毁前一直有效,因此可以持有引用或指针而无需拷贝;
  • 必须定义template <class Callback> void format(FormatArg& arg, Callback& cb) const;
  • arg以非 const 引用给出,允许就地修改(例如包裹已有转换但修改默认值,或在从容器取元素时移除 key);
  • cb是输出回调,可以多次调用(甚至不调用,用于输出空字符串)。

Formatter类还有第二层扩展点:通过继承BaseFormatter并重写doFormatArgrecordUsedArg可以自定义对位置参数的实际格式化行为(folly/Format.h)。测试文件中的TestExtendingFormatter正是这样做的:它在派生类里重写doFormatArg,先把每个参数按默认规则格式化,再用fmt::format("{{{}}}", result)包上一层花括号(folly/test/FormatTest.cpp);Custom测试则验证了自定义类型KeyValue特化后配合{:10}{:.10}{:X<23}{:X>23}等宽度/对齐/截断规范的组合行为(folly/test/FormatTest.cpp)。

错误处理与防御性工具

非法格式串与 BadFormatArg

格式串解析与校验在FormatArg中完成,非法用法统一抛出BadFormatArg(继承自std::invalid_argument,folly/FormatArg.h)。测试用例BogusFormatString系统性地验证了这些错误路径(folly/test/FormatTest.cpp):

  • 单独出现的}"single '}' in format string"
  • 缺少结尾}"missing ending '}'"
  • 负数参数索引:"argument index must be non-negative"
  • 显式索引与默认索引混用:"may not have both default and explicit arg indexes"
  • 动态宽度参数不是整数:"dynamic field width argument must be integral"
  • 参数索引越界:"argument index out of range, max=N"

缺失键与 FormatKeyNotFoundException

当字符串键映射中找不到 key 时,KeyableTraitsAssoc::at会抛出专门的FormatKeyNotFoundException(继承std::out_of_range,folly/Format-inl.h)。该类特意把 key 放在异常消息末尾,使异常类型保持小巧且可 noexcept 拷贝(folly/Format.h)。

defaulted():越界时返回默认值

folly::defaulted(container, defaultValue)把容器包装成DefaultValueWrapper,使越界下标或缺失键不再抛异常而是返回指定默认值(folly/Format.h)。例如文档中的用法format("[no_such_key"], defaulted(map, 42))会得到42。该包装对可索引容器(folly/Format-inl.h)、字符串键映射(folly/Format-inl.h)以及dynamic(folly/json/dynamic-inl.h)均有专门实现,后者在数组下标越界或对象键缺失时输出默认值。

底层实现要点

FormatArg:一次解析、处处复用

FormatArg是格式化的核心中间结构(folly/FormatArg.h),它持有格式串引用(不拷贝字符),并把fillalignsignbasePrefixthousandsSeparatortrailingDotwidthwidthIndexprecisionpresentation等解析结果全部展开为字段。对齐与符号分别用AlignSign枚举表达,非法值由validate(Type)在类型校验阶段拦截。

编译期查表优化

在 folly/Format.cpp 中,对齐表、符号表、十六进制(大小写各一)、八进制、二进制的转换均以constexpr构造的查表实现(如make_array_with<256>(...)),把常见的数字到 ASCII 转换压成一次查表,这也是文档所称"许多场景下比sprintf更快"的实现基础。

数字格式化的两条路径

format_value::formatNumber把符号、进制前缀等"前缀"与数字主体分开处理,以支持PAD_AFTER_SIGN这类"符号后填充"的对齐方式(folly/Format-inl.h)。整数FormatValue的统一实现把所有计算转为无符号进行,再自行附加前缀与符号,并特别规避了无符号取负的未定义行为(folly/Format-inl.h)。浮点double的实现(folly/Format.cpp)默认精度为 6 位;当用户写裸{}(无任何说明符与精度)时,走最短往返(shortest round-trip)表示以保持数值精度,而显式给出说明符或精度时走基于精度的路径,精度上限与 double-conversion 保持一致(小数后最多 100 位、指数最多 120 位)。long double未实现,这是官方文档明确说明的边界。

延迟输出模型

Formatter采用回调式输出:format()不立即生成字符串,而是把"格式串 + 参数引用"打包成Formatter对象,直到真正写入流、追加到字符串或调用.str()时才逐段计算(folly/Format.h)。这既避免了多余中间字符串,也让"直接流式输出"成为可能;同时Formatter不可拷贝、只能由format()工厂构造(移动构造私有),从机制上防止了悬垂引用风险。

现状与迁移提示

需要说明的是,源码头部注释明确指出:folly::format已被 fmt 取代,推荐使用#include <fmt/core.h>(folly/Format.h)。folly::format入口本身也带有[[deprecated("Use fmt::format instead of folly::format ...")]]标记(folly/Format.h),建议新代码直接使用fmt::format以获得更好的性能、更短的编译时间以及与std::format的兼容性。仓库中的示例程序 folly/docs/examples/folly/Format.cpp 也已改用fmt::format(如fmt::format("The answer to {} is {}", "life", 42)得到"The answer to life is 42"fmt::format("{:.2f}", 3.1415926535)得到"3.14")。

不过,folly::format的格式串语法、容器取值、defaulted()防御式默认值以及FormatValue扩展模型,依然是理解 folly 格式化体系与迁移路径的最佳教材;在维护存量 folly 代码时,这份文档与上述源码、测试(folly/test/FormatTest.cpp)共同构成了最完整的参考资料。

【免费下载链接】follyAn open-source C++ library developed and used at Facebook.项目地址: https://gitcode.com/GitHub_Trending/fol/folly

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询