(原文链接:https://abseil.io/tips/3 译者:clangpp@gmail.com)
每周贴士 #3: 字符串拼接,operator+对比StrCat()
- 最初发布于2012-05-11
- 更新于2017-09-18
- 校订于2018-01-22
当审查代码的的人对程序员说“别用字符串拼接操作符,太低效了”,程序员常常会一脸吃惊:std::string::operator+怎么会低效呢?难道这不是很难出错的方式吗?
事实证明,这种低效没有那么简单明了。在实践中,下面两段代码执行时间相差不多:
std::string foo = LongString1();
std::string bar = LongString2();
std::string foobar = foo + bar;
std::string foo = LongString1();
std::string bar = LongString2();
std::string foobar = absl::StrCat(foo, bar);
然而,同样的结论却不适用于以下两段代码:
std::string foo = LongString1();
std::string bar = LongString2();
std::string baz = LongString3();
string foobar = foo + bar + baz;
std::string foo = LongString1();
std::string bar = LongString2();
std::string baz = LongString3();
std::string foobar = absl::StrCat(foo, bar, baz);
为什么这两个例子中的执行时间比较会有不同?我们可以通过拆解表达式foo + bar + baz得到答案。C++中没有“三个参数”的操作符重载,因此这个表达式必须调用两次string::operator+。在这两次调用过程中,会有一个临时字符串被构造(和存储)。所以std::string foobar = foo + bar + baz等价于:
std::string temp = foo + bar;
std::string foobar = std::move(temp) + baz;
请注意,在foobar被赋值以前,foo和bar存储的内容必须被复制到一个临时区域中。(更多关于std::move的内容,请参阅Tip of the Week #77: Temporaries, moves, and copies.)
C++11至少允许第二个拼接不需要创建一个新的string对象:std::move(temp) + baz等价于std::move(temp.append(baz))。但是,那个临时字符串(temp,译者注)最初申请的空间可能不足以存下最终的字符串内容,这种情况下会重新申请一次内存(reallocation)(以及复制一次)。因此,在最坏情况下,n次字符串拼接需要重新申请O(n)次内存。
这时最好改用absl::StrCat(),一个不错的辅助函数(定义在absl/strings/str_cat.h中)。它会计算必要的字符串长度,按此预留空间,然后将所有输入数据放进输出的空间中(优化过的O(n)时间复杂度)。类似地,对于如下情况:
foobar += foo + bar + baz;
可以使用absl::StrAppend()(实现了类似优化):
absl::StrAppend(&foobar, foo, bar, baz);
另外,absl::StrCat()和absl::StrAppend()还接收非字符串类型:你可以用absl::StrCat/absl::StrAppend转换int32_t,uint32_t,int64_t,uint64_t,float,double,const char*和string_view(转换为string,译者注),例如:
std::string foo = absl::StrCat("The year is ", year);

本文探讨了C++中std::string的operator+与absl::StrCat在字符串拼接效率上的差异,尤其是在多次拼接时。通过分析foo+bar+baz的底层实现,揭示了多次调用string::operator+导致的性能瓶颈。文章推荐使用absl::StrCat和absl::StrAppend进行优化,以避免不必要的内存分配和复制。
835

被折叠的 条评论
为什么被折叠?



