每周技巧 #18:使用 Substitute 进行字符串格式化

本节阅读量:

本文翻译自 Abseil 官网的 Tip of the Week #18: String Formatting with Substitute

原文最初作为 TotW #18 发布于 2012 年 10 月 4 日。

作者:Titus Winters

更新于 2022 年 11 月 16 日。

这种事情总会发生:你正在写代码,突然需要根据一个模板和一些运行时值组装出一个新字符串。也许这是一次失败 Stubby 调用的错误消息,也许是某个内部流程发送邮件的正文。在 google3 之外,字符串格式化最常见的机制大概是 sprintf/snprintf。但当我们在代码库中穿行时,会看到人们做过很多事情,也会看到很多地方里,C++ 工程师为了完成这个任务花了太多时间、太多周期和太多代码行。本周技巧会走过几个常见选项,并指出它们各自的缺点。

方案 1:字符串拼接(内置)

有些人仍然会直接使用基本的字符串拼接:

1
2
3
4
5
std::string GetErrorMessage(const std::string& op, const std::string& user,
                            int id) {
  return "Error in " + op + " for user " + user + "(" + std::to_string(id) +
         ")";
}

正如我们在 技巧 #3 中看到的,这种方法有问题:对 operator+() 的链式调用最终会创建并浪费临时对象,还会导致对数据的不必要拷贝。

方案 2:absl::StrCat()absl/strings/str_cat.h

就像技巧 #3 里说的,absl::StrCat() 可以避免这些拷贝,替我们处理数值转换,并且允许我们高效地操作 string_view(当调用方传入 C 风格字符串时,这一点更好):

1
2
3
4
std::string GetErrorMessage(absl::string_view op, absl::string_view user,
                            int id) {
  return absl::StrCat("Error in ", op, " for user ", user, "(", id, ")");
}

不过,一眼看清这个字符串最终到底长什么样仍然有点困难:空格在哪里?字符串里的括号是否正确匹配?

方案 3:absl::Substituteabsl/strings/substitute.h

不过,还有一个更好的选项:absl::Substitute()Substitute() 使用和 StrCat() 相同的技术,让你可以传入数值、std::stringchar*string_view。它不要求你记住用于数值类型(或 string_view)的晦涩格式字符串。

虽然大家或多或少都能理解 printf 风格的格式字符串,但 Substitute 的格式字符串同样不容易被误读:

1
2
3
4
std::string GetErrorMessage(absl::string_view op, absl::string_view user,
                            int id) {
  return absl::Substitute("Error in $0 for user $1 ($2)", op, user, id);
}

可读性好,性能也更好?LGTM。

每周技巧 #11:返回策略

上一节

每周技巧 #24:拷贝,简写版

下一节