【问题标题】:C++ best practice of concat strings连接字符串的 C++ 最佳实践
【发布时间】:2018-12-18 14:11:40
【问题描述】:

如果经常连接字符串以形成最终结果,最好的做法是什么? (在 C++11 或更高版本中)

string::append()ostringstream 呢?

还有一个问题:如果我使用许多+ 来连接字符串,现代编译器会以最有效的方式对其进行优化吗? (就像Java现在可以使用StringBuilder优化字符串concat)

【问题讨论】:

  • 我个人会对其进行编码,使其看起来不错。然后对其进行分析并确定性能是否适合您。
  • 查看这个关于 c++ 相当于 Java 的 String Builder 的问题:stackoverflow.com/questions/2462951/…
  • 我使用s1 += s2,我确信它与s1.append(s2) 相同。如果需要,我会避免 std::stringstream 是速度,因为它相对较慢。
  • 使用string::operator+=。它可能与string::append 完全相同。 stringstream 有不同的用途。
  • 在内部std::string 可能更像StringBuilder 因为Java String 是只读的,所以它们不能被附加到。 std::string,另一方面可以。

标签: c++ c++11


【解决方案1】:

如果您以某种方式想到托管领域中的 StringBuilder。

您可以使用 Alphabet (Google) Library、ABCLib、ABCL 或只是 Abseil。

Abseil's Strings library 向前看并立即分配所需的所有内容,然后根据需要在其中构建字符串。对于 concat 作业,您只需要 absl::StrCat() 和 absl::StrAppend()。

我不擅长解释事情。也许下面这个godbolt 链接可能比我说得更好。

godbolt.org/g/V45pXJ

在 YouTube 上了解更多信息:CppCon 2017: Titus Winters “Hands-On With Abseil”(ffw 至 32 分钟)

youtube.com/watch?v=xu7q8dGvuwk&t=32m

#include <string>
#include <iostream>
#include <absl/strings/str_cat.h>

int main()
{
    std::string s1,s2,s3,s4,
                s5,s6,s7,s8,
                s9,s10,s11,s12;
    std::getline(std::cin, s1);
    std::getline(std::cin, s2);
    std::getline(std::cin, s3);
    std::getline(std::cin, s4);
    std::getline(std::cin, s5);
    std::getline(std::cin, s6);
    std::getline(std::cin, s7);
    std::getline(std::cin, s8);
    std::getline(std::cin, s9);
    std::getline(std::cin, s10);
    std::getline(std::cin, s11);
    std::getline(std::cin, s12);
    std::string s = s1 + s2 + s3 + s4 +  // a call to operator+ for each +
                    s5 + s6 + s7 + s8 +
                    s9 + s10 + s11 + s12;

    // you shall see that
    // a lot of destructors get called at this point
    // because operator+ create temporaries

    std::string abseil_s = 
       absl::StrCat(s1,s2,s3,s4,  // load all handles into memory
                    s5,s6,s7,s8,  // then make only one call!
                    s9,s10,s11,s12);

    return s.size() + abseil_s.size();

    // you shall see that
    // only "real" s1 - s12 get destroyed
    // at these point
    // because there are no temporaries!

}


2021 年更新

今天,当 c++20 library 实现完成时,您可以选择使用 fmt:formatstd::format。 (当前 fmtlib 现在支持 c++14。)

format 会像 Abseil StrCat 一样向前看,所以不会浪费临时工。

    string fmt_s = 
        fmt::format("{}{}{}{}{}{}{}{}{}{}{}{}",
                    s1,s2,s3,s4,  // load all handles into memory
                    s5,s6,s7,s8,  // then make only one call!
                    s9,s10,s11,s12);

[LIVE]

【讨论】:

  • godbolt 链接坏了……或者至少,它把我带到了一个空的 godbolt.org 页面。
  • @MatteoItalia 确实如此。抱歉,我没有重新检查。已经修好了。
  • 我打开这个答案是为了提到StrCat。谢谢你写它!
【解决方案2】:

如果您不需要std::ostringstream 的任何功能,如果您的最终目标是获得单个std::string(并调用reserve提前充分可以减少一些无用的分配)。在内部,std::ostringstream 的工作方式大致相同,但它为额外的层增加了一些开销。

还有一个问题:如果我使用许多+ 来连接字符串,现代编译器会以最有效的方式对其进行优化吗? (就像Java现在可以使用StringBuilder优化字符串concat)

std::string 已经类似于StringBuilder,因为它是一种高效增长的可变类型,因此您无需为此使用其他类型。

现在,在 C++03 中,多个串联的 + 将创建所有相关的临时字符串,因此如果您想保持高效,您必须在目标字符串上反复显式使用 +=

OTOH,C++11 添加了operator+ 的重载,采用右值引用,这允许为临时字符串分配的存储空间被回收/扩展以用于下一次连接,因此在大多数情况下效率应该是相当的 - 感谢@Daniel Schepler 指出了这一点。

我认为这会失败的一种情况是:

big_string += a + b + c;

在这种情况下,a + b + c 本身的计算效率很高,但没有考虑它将被附加到另一个字符串的事实(因此,可能没有任何额外的分配)。您最好使用“传统”方法或

big_string = std::move(big_string) + a + b + c;

【讨论】:

  • 如果使用 C++11,string::operator+ 有几个涉及右值的重载,这些重载将能够将分配的存储从一个临时值重用到下一个。
  • @DanielSchepler 哇,这是我在 8 年多的 C++11 中设法错过的东西。修复答案...
  • @DanielSchepler @MatteoItalia 这安全吗?我猜这个基于右值的operator+ 返回一个对字符串本身的右值引用,但是stackoverflow.com/a/66952566/6222803std::string 不是MoveAssignable
  • @TuffContender:operator+(number 7)返回一个与big_string不同的实际对象,所以应该不存在自赋值问题;返回值也是一个右值,所以它应该总是在随后的+ 调用中触发相同的重载,并在最后触发移动赋值运算符 (number 2)。因此,应该没有问题,也没有任何额外的副本。
猜你喜欢
  • 2017-09-07
  • 1970-01-01
  • 2017-12-01
  • 1970-01-01
  • 2010-10-11
  • 2011-03-17
  • 1970-01-01
  • 2014-04-01
  • 1970-01-01
相关资源
最近更新 更多