每周技巧 #124:`absl::StrFormat()`
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #124: absl::StrFormat()。
原文最初作为 TotW #124 发布于 2016 年 10 月 11 日。
更新于 2022 年 11 月 16 日。
快捷链接:abseil.io/tips/124
str_format 库和 absl::StrFormat()
经过长期测试和开发后,我们很高兴宣布 str_format 库现在已经正式可用。str_format 是一个非常高效、类型安全且可扩展的库,实现了全部 printf 格式化语法。几乎所有 printf 风格的转换都可以轻松升级为 absl::StrFormat()。更详细的文档见 https://abseil.io/docs/cpp/guides/format。对于 printf 风格的格式化,它是最佳选择;不过本文不讨论 printf 风格在哪些地方适合或不适合。
用法很简单。添加对 //third_party/absl/strings:str_format 的 BUILD 依赖,并包含头文件:
|
|
大多数用户会像过去调用 StringPrintf() 或 util::format::StringF() 一样,直接调用 absl::StrFormat() 来使用 str_format 库。此外还有 StrAppendFormat() 和 StreamFormat() 变体。
|
|
与 C 库的 printf() 不同,absl::StrFormat() 转换的正确性不依赖调用方把实参的精确类型编码进格式字符串。使用 printf() 时,必须通过长度修饰符和转换说明符小心完成这一点,例如用 %llu 表示 unsigned long long 类型。但 absl::StrFormat() 是用 C++ 写的,因此它使用模板和重载,直接安全地处理调用方实参列表中的类型。在 absl::StrFormat() 中,格式转换指定的是更宽泛的 C++ 概念类别,而不是某个精确类型。例如,%s 会绑定到任何类似字符串的实参,因此 std::string、absl::string_view、Cord 和 const char* 都可以接受。同样,%d 接受类似整数的实参,等等。它还可以为基本用户自定义类型进一步扩展(不过目前我们希望先由我们管理这些扩展)。它会忽略 ll 这样的长度修饰符,并格式化任何可用值。例如,在下面代码里,客户端不必不必要地硬编码 x 的数据成员类型:
|
|
name 可以是任何类似字符串的东西,size 可以是任何类似整数的类型。这种解耦对 project_x 的维护者非常有利。
借助 str_format 库,我们还可以更顺滑地控制输出目的地。在 printf() 家族中,fprintf() 用于 FILE* 输出,sprintf() 用于写入缓冲区,asprintf() 用于写入已分配内存,还有现在已废弃的 StringPrintf() 用法(会浪费性地多次调用 vsnprintf())。str_format 库使用抽象 sink,因此可以在不损失效率的前提下自定义目的地。作为内置能力,我们有用于生成新 std::string 的 absl::StrFormat(),用于追加到 std::string 的 absl::StrAppendFormat(),以及用于写入 std::ostream(例如日志)的 absl::StreamFormat()。
在 clang 编译器下,字面量格式字符串会进行编译期检查。对于少见的运行时决定格式字符串的情况,格式字符串必须先解析,并根据实参列表规格检查兼容性,然后才能使用。这消除了传统 printf() 使用运行时格式时的一种危险。生成已解析格式说明符的能力(类似正则表达式可以编译成 RE 对象)可以带来性能提升,因此在性能敏感代码中,即使格式字符串是静态确定的,也可能会使用它。
它与 printf() 有一些值得注意的差异(见 https://abseil.io/docs/cpp/guides/format)。我们在 str_format 库中试图做到灵活而不丢失信息。如果有符号实参使用 %u 或 %x 这样的无符号转换进行格式化,我们会先把实参转换为对应的无符号整数类型再格式化,因此用 %u 打印负数时,其行为可能不同于你对这个先前未定义行为的预期。
最重要的是,它经过高度优化,比 sprintf() 或已废弃的 StringPrintf() 快得多(见 format-shootout)。请在任何会使用 printf 风格格式化的地方试试这个库。
示例
最后给出几个示例。
|
|