每周技巧 #215:使用 `AbslStringify()` 将自定义类型字符串化
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #215: Stringifying Custom Types with AbslStringify()。
原文最初作为 TotW #215 发布于 2022 年 11 月 2 日。
作者:Phoebe Liang
更新于 2022 年 11 月 16 日。
快捷链接:abseil.io/tips/215
Abseil 现在包含一种新的轻量机制 AbslStringify(),允许用户把用户自定义类型格式化为字符串。使用 AbslStringify() 扩展的用户自定义类型,可以开箱即用于 absl::StrFormat、absl::StrCat 和 absl::Substitute。
与大多数类型扩展一样,你应该拥有你想扩展的类型。
假设我们有一个简单的 Point 结构体:
|
|
如果希望 Point 可以被 absl::StrFormat()、absl::StrCat() 和 absl::Substitute() 格式化,我们添加一个名为 AbslStringify() 的 friend 函数模板:
|
|
注意:AbslStringify() 使用一个通用 “sink” 缓冲区构造字符串。这个 sink 的接口类似于 absl::FormatSink,但不支持 PutPaddedString()。
现在 absl::StrCat("The point is ", p) 和 absl::Substitute("The point is $0", p) 会直接工作。
注意:absl::StrFormat() 还提供一个更可自定义的扩展点 AbslFormatConvert(),但它不受 absl::StrCat() 支持。
使用 %v 说明符进行类型推导
但如果我们想用 absl::StrFormat() 格式化这个类型呢?absl::StrFormat() 现有的类型说明符不支持使用 AbslStringify() 扩展的用户自定义类型,所以我们不得不写成这样:
|
|
这显然不理想。它完全没有使用这个扩展,而且重复了 AbslStringify() 定义中的格式字符串。我们可以改用新的类型说明符 %v:
|
|
%v 使用类型推导来格式化实参。%v 支持大多数基本类型,以及任何用 AbslStringify() 扩展的类型。你可以把 %v 看作一种泛型方式,用来格式化 absl::StrFormat() 可以推导出的任意类型的“值”。%v 也可以直接用在 AbslStringify() 定义中。
%v 说明符推导以下类型:
- 对有符号整数值推导为
d - 对无符号整数值推导为
u - 对浮点值推导为
gdoublefloatlong double
- 对字符串值推导为
sstd::stringabsl::string_viewstd::string_viewabsl::Cord
注意:不支持 const char*。更多信息见下文。
一些例子:
|
|
有特殊处理的类型
由于期望输出格式存在歧义,%v 有意不支持 char 和 const char*。布尔值会打印为 "true" 和 "false",而不是 "1" 和 "0";后者是 absl::StrFormat() 和 absl::StrCat() 在其他情况下打印布尔值的方式。
与其他库集成
AbslStringify() 在其他 Abseil 库中也有额外支持。
日志
定义了 AbslStringify() 的类型可以直接写入日志:
|
|
这段代码会在日志中产生类似下面的消息:
|
|
建议通过实现 AbslStringify() 而不是 operator<< 让自定义类型可记录日志,因为它是一种通用字符串化扩展,同时还能启用 absl::StrFormat、absl::StrCat 和 absl::Substitute 支持。
Protocol Buffer 类型
Protocol buffers 可以使用 AbslStringify() 格式化。由于 AbslStringify() 相比 DebugString() 提供了整体更顺滑的用户体验,建议用户在把 proto 格式化为字符串时使用 AbslStringify()。
|
|
结束语
除了需要它的用户自定义类型之外,%v 类型说明符旨在用于那些精确格式并不重要的场景。它本质上是一个兜底的“以人类可读方式打印它”说明符。如果你需要除此之外的任何其他保证,请使用更具体的类型说明符。