fmt 7.1.3

fmt 7.1.3

Hien Nguyen 维护。



fmt 7.1.3

  • fmt 贡献者

{fmt}

https://travis-ci.org/fmtlib/fmt.png?branch=master https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v fmt is continuously fuzzed att oss-fuzz Ask questions at StackOverflow with the tag 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"

创建类似于 formatprint 的函数,这些函数可以接受任意参数(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::ostringstreamsprintf 快 10 倍(dtoa-benchmark)和与 double-conversion 相当(链接)。

https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png

编译时间和代码膨胀

来自 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+stringprintf相同,但增加了额外的<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

libclib(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 SafeFormatClang 的诊断 API 的一些想法。格式字符串语法和文档基于 Python 的 str.format。感谢 Doug Turnbull 对类型安全 API 的设计提供的宝贵评论和贡献,以及 Gregory Czajkowski 对二进制格式化功能的实现。感谢 Ruslan Baratov 对整数格式化算法的全面比较以及对性能的宝贵评论,Boris KaulC++ 计数工具基准测试 的贡献。感谢 CarterLi 对代码做贡献。