{fmt}
{fmt} 是一个用于 C++ 的开源格式化库。它可以作为一个安全且快速的 (s)printf 和 iostreams 的替代品。
Q&A:在 StackOverflow 上贴上带有 fmt 标签的问题。
功能
- 用于本地化的基于替换的格式 API,并具有位置参数。
- 类似于 Python 中的 str.format 的格式字符串语法。
- 包含 POSIX 扩展的位置参数的 printf 实现。
- 实现了 C++20 std::format。
- 支持用户自定义类型。
- 高性能:比常见的标准库实现更快,包括 速度测试 和 快速的整数到字符串转换。
- 小的源代码大小和编译代码大小。另请参阅 编译时间和代码膨胀。
- 可靠性:该库有一套广泛的 单元测试,并且持续进行模糊测试。
- 安全性:库是完全类型安全的,可以在编译时报告格式字符串中的错误,自动内存管理防止缓冲区溢出错误。
- 易用性:小型独立代码库,无外部依赖项,MIT 许可证 使用许可 开放。
- 可在多个平台之间进行端口统一,并支持旧编译器。
- 干净的警告无代码库,即使在高级警告级别(例如
-Wall -Wextra -pedantic
)下也是如此。 - 支持宽字符串。
- 使用
FMT_HEADER_ONLY
宏启用可选的头文件仅配置。
有关更多详细信息,请参阅 文档。
示例
将 Hello, world!
打印到 stdout
fmt::print("Hello, {}!", "world"); // Python-like format string syntax
fmt::printf("Hello, %s!", "world"); // printf format string syntax
格式化字符串并使用位置参数
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
// s == "I'd rather be happy than right."
在编译时检查格式字符串
// test.cc
#include <fmt/format.h>
std::string s = format(FMT_STRING("{2}"), 42);
$ c++ -Iinclude -std=c++14 test.cc ... test.cc:4:17: note: in instantiation of function template specialization 'fmt::v5::format<S, int>' requested here std::string s = format(FMT_STRING("{2}"), 42); ^ include/fmt/core.h:778:19: note: non-constexpr function 'on_error' cannot be used in a constant expression ErrorHandler::on_error(message); ^ include/fmt/format.h:2226:16: note: in call to '&checker.context_->on_error(&"argument index out of range"[0])' context_.on_error("argument index out of range"); ^
使用 {fmt}
作为安全便携的 itoa
替代方案(godbolt)
fmt::memory_buffer buf;
format_to(buf, "{}", 42); // replaces itoa(42, buffer, 10)
format_to(buf, "{:x}", 42); // replaces itoa(42, buffer, 16)
// access the string with to_string(buf) or buf.data()
通过简单的 扩展API 格式化用户定义类型的对象
#include "fmt/format.h"
struct date {
int year, month, day;
};
template <>
struct fmt::formatter<date> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const date& d, FormatContext& ctx) {
return format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day);
}
};
std::string s = fmt::format("The date is {}", date{2012, 12, 9});
// s == "The date is 2012-12-9"
创建类似于 format 和 print 的函数,这些函数可以接受任意参数(godbolt)
// Prints formatted error message.
void vreport_error(const char* format, fmt::format_args args) {
fmt::print("Error: ");
fmt::vprint(format, args);
}
template <typename... Args>
void report_error(const char* format, const Args & ... args) {
vreport_error(format, fmt::make_format_args(args...));
}
report_error("file not found: {}", path);
注意,vreport_error
不是根据参数类型参数化的,这可以改善编译时间和减少代码大小,相对于完全参数化的版本。
基准测试
速度测试
库 | 方法 | 运行时间,秒 |
---|---|---|
libc | printf | 1.04 |
libc++ | std::ostream | 3.05 |
{fmt} 6.1.1 | fmt::print | 0.75 |
Boost Format 1.67 | boost::format | 7.24 |
Folly Format | folly::format | 2.23 |
{fmt} 是基准测试中最快的方法,比 printf
快 ~35%。
以上结果是通过在 macOS 10.14.6 上构建 tinyformat_test.cpp
并使用 clang++ -O3 -DSPEED_TEST -DHAVE_FORMAT
生成的,并取了三次运行中的最好成绩。在该测试中,格式字符串 "%0.10f:%04d:%+g:%s:%p:%c:%%\n"
或等效的字符串被填充了 2,000,000 次,输出被发送到 /dev/null
;有关更多信息请参阅 源代码。
{fmt} 在浮点格式化上比 std::ostringstream
和 sprintf
快 10 倍(dtoa-benchmark)和与 double-conversion 相当(链接)。

编译时间和代码膨胀
来自 format-benchmark 的脚本 format-benchmark 测试了非平凡项目的编译时间和代码膨胀。它生成100个翻译单元,并在每个单元中使用5次 printf()
或其替代方案来模拟中等规模的项目。以下表格显示了结果的可执行文件大小和编译时间(Apple LLVM 8.1.0 (clang-802.0.42),macOS Sierra,三试中的最优结果)。
优化构建(-O3)
方法 | 编译时间,秒 | 可执行文件大小,KiB | 缩减后的文件大小,KiB |
---|---|---|---|
printf | 2.6 | 29 | 26 |
printf+字符串 | 16.4 | 29 | 26 |
iostreams | 31.1 | 59 | 55 |
{fmt} | 19.0 | 37 | 34 |
Boost Format | 91.9 | 226 | 203 |
Folly Format | 115.7 | 101 | 88 |
如您所见,与iostreams相比,{fmt}在结果二进制代码大小方面的开销减少了60%,并接近printf
。Boost Format和Folly Format的开销最大。
printf+string
与printf
相同,但增加了额外的<string>
包含,以测量后者的开销。
非优化构建
方法 | 编译时间,秒 | 可执行文件大小,KiB | 缩减后的文件大小,KiB |
---|---|---|---|
printf | 2.2 | 33 | 30 |
printf+字符串 | 16.0 | 33 | 30 |
iostreams | 28.3 | 56 | 52 |
{fmt} | 18.2 | 59 | 50 |
Boost Format | 54.1 | 365 | 303 |
Folly Format | 79.9 | 445 | 430 |
libc
,lib(std)c++
和libfmt
都链接为共享库,仅比较格式化函数的开销。Boost Format是仅头文件的库,因此不提供任何链接选项。
运行测试
请参阅 构建库 指导构建库和运行单元测试的说明。
基准测试位于独立的仓库中,format-benchmarks,因此要运行基准测试,首先需要克隆此存储库并使用CMake生成Makefiles
$ git clone --recursive https://github.com/fmtlib/format-benchmark.git $ cd format-benchmark $ cmake .
然后您可以运行速度测试
$ make speed-test
或膨胀测试
$ make bloat-test
使用此库的项目
- 0 A.D.:一款免费、开源、跨平台的实时战略游戏
- AMPL/MP:数学规划的开源库
- AvioBook:一个综合的飞机运营套件
- Celestia:太空实时3D可视化
- Ceph:可伸缩的分布式存储系统
- ccache:编译器缓存
- CUAUV:康奈尔大学的自主水下航行器
- HarpyWar/pvpgn:玩家对战游戏网络及其修改版
- KBEngine:开源MMOG服务器引擎
- Keypirinha:Windows的语义启动器
- Kodi(原名xbmc):家庭影院软件
- Lifeline:一款2D游戏
- Drake:非线性动力学系统的规划、控制和分析工具包(麻省理工学院)
- Envoy:C++ L7代理和通信总线(Lyft)
- FiveM:GTA V的修改框架
- MongoDB:分布式文档数据库
- MongoDB Smasher:一个用于生成随机数据集的小工具
- OpenSpace:一个开源的宇宙可视化框架
- PenUltima Online (POL):一个支持大多数Ultima Online客户端的MMO服务器
- quasardb:一个分布式、高性能的关联数据库
- readpe:读取可移植执行文件
- redis-cerberus:一个Redis集群代理
- rpclib:一个现代C++ msgpack-RPC服务器和客户端库
- Saddy:一个小型跨平台2D图形引擎
- Salesforce Analytics Cloud:商业智能软件
- Scylla:一个可与Cassandra兼容的NoSQL数据存储,可以在单个服务器上处理每秒一百万笔交易
- Seastar:一个高级的开源C++框架,用于在现代硬件上构建高性能服务器应用程序
- spdlog:超快C++日志库
- Stellar:金融平台
- Touch Surgery:手术模拟器
- TrinityCore:开源MMORPG框架
如果您知道其他使用此库的项目,请通过电子邮件或提交一个问题让我知道。
动机
那么为什么还要另一个格式化库呢?
有大量的方法来完成这个任务,从标准的方法,如printf函数族和iostreams,到Boost Format和FastFormat库。创建新库的原因是,我找到的每个现有解决方案要么有严重的问题,要么没有提供我需要的所有功能。
printf
printf的好处是它相当快,并且作为C标准库的一部分,容易使用。主要缺点是不支持用户定义的类型。printf
也存在安全性问题,尽管GCC中的__attribute__ ((format (printf, ...)))
在一定程度上缓解了这些问题。POSIX扩展添加了针对i18n所需的格式化参数到printf,但它不是C99标准的一部分,可能在某些平台上不可用。
iostreams
iostreams的主要问题最好通过一个例子来阐明。
std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
与printf相比,它需要输入更多。
printf("%.2f\n", 1.23456);
FastFormat的作者Matthew Wilson称这种现象为“箭头地狱”。iostreams在设计上不支持位置参数。
好处是iostreams支持用户自定义类型,虽然错误处理有些麻烦,但仍然很安全。
Boost Format
这是一个功能强大的库,它支持类似于printf的格式字符串和位置参数。其主要缺点是性能。根据各种基准测试,它的速度远低于这里考虑的其他方法。Boost Format的构建时间过长,代码膨胀问题严重(见基准测试)。
FastFormat
这是一个既快又安全且具有位置参数的有趣库。然而,它有一些显著的局限性,如其作者的描述。
以下三个特性无法在当前设计中实现。
- 前导零(或任何其他非空格填充)
- 八进制/十六进制编码
- 运行时宽度和对齐指定
它还很庞大,而且有重依赖关系STLSoft,这可能使得在某些项目中使用它变得过于受限。
Boost Spirit.Karma
这实际上不是一个格式化库,但我决定将其包含在这里以保持完整性。与iostreams一样,它存在将文本与参数混合的问题。该库运行速度相当快,但在Karma自己的基准测试中,整数格式化比fmt::format_int慢(见Fast integer to string conversion in C++)。
常见问题解答
问题:我如何捕获格式化参数并在以后进行格式化?
答案:使用 std::tuple
template <typename... Args>
auto capture(const Args&... args) {
return std::make_tuple(args...);
}
auto print_message = [](const auto&... args) {
fmt::print(args...);
};
// Capture and store arguments:
auto args = capture("{} {}", 42, "foo");
// Do formatting:
std::apply(print_message, args);
许可证
{fmt} 以 MIT 许可证发行。
文档中关于 格式字符串语法 的部分基于 Pyton string 模块文档改编的,适用于当前库。因此,该文档以 Python 软件基金会许可证发行,可在 doc/python-license.txt 中找到。仅在您分发 fmt 的文档时适用。
致谢
{fmt} 库由 Victor Zverovich (vitaut) 和 Jonathan Müller (foonathan) 维护,许多其他人为之做出贡献。请参阅 贡献者 和 发布记录 以查看部分名称。如果您的贡献未列出或描述错误,请告知我们,我们将更正。
此说明文件中的基准测试部分和对性能的测试取自 Chris Foster 编写的优秀tinyformat 库。Boost 格式库因对 tinyformat 产生了一定影响而被间接认可。实现中使用了来自 Loki SafeFormat 和 Clang 的诊断 API 的一些想法。格式字符串语法和文档基于 Python 的 str.format。感谢 Doug Turnbull 对类型安全 API 的设计提供的宝贵评论和贡献,以及 Gregory Czajkowski 对二进制格式化功能的实现。感谢 Ruslan Baratov 对整数格式化算法的全面比较以及对性能的宝贵评论,Boris Kaul 对 C++ 计数工具基准测试 的贡献。感谢 CarterLi 对代码做贡献。