从char*初始化std::string而不复制

53

我有一个情况需要处理大量(几 GB)的数据,具体如下:

  1. 通过添加许多较小的(C char*)字符串来构建一个大字符串
  2. 修剪该字符串
  3. 将该字符串转换为 C++ const std::string 以进行处理(只读)
  4. 重复执行此过程

每次迭代中的数据是独立的。

我的问题是,我想尽量减少(如果可能的话消除)堆内存的使用,因为目前这是我最大的性能问题。

是否有一种方法可以将 C 字符串(char*)转换为 stl C++ 字符串(std::string),而无需要求 std::string 在内部分配/复制数据?

或者,我可以使用 stringstream 或类似的东西来重用一个大缓冲区吗?

编辑:感谢答案,为了更清晰,我认为修改后的问题应该是:

如何高效地构建(通过多次追加)stl C++字符串。如果在循环中执行此操作,其中每个循环都是完全独立的,则如何重复使用已分配的空间。

6个回答

30
您实际上无法形成std :: string而不复制数据。stringstream可能会重用从传递到传递的内存(尽管我认为标准对它是否必须这样做保持沉默),但它仍然无法避免复制。
解决此类问题的常见方法是编写处理步骤3中的数据的代码,以使用begin / end迭代器对;然后它可以轻松地处理std :: string,char向量,原始指针对等等。与传递容器类型(如std :: string)不同,它将不再知道或关心内存是如何分配的,因为它仍将属于调用者。将这个想法推向其逻辑结论就是boost :: range,它添加了所有重载的构造函数,仍然让调用者只需通过具有.begin()和.end()的字符串/向量/列表/任何类型的容器或单独的迭代器即可传递。
在编写处理任意迭代器范围的处理代码之后,您甚至可以编写自定义迭代器(不像听起来那么难,基本上只是带有一些标准typedef和操作符++ / * / = / == /!= 重载以获取仅向前迭代器的对象),该迭代器负责每次命中正在处理的片段的末尾时前进到下一个片段,跳过空格(我假设这是您所说的修剪)。您根本不必将整个字符串连续组装在一起。无论这是否胜利取决于您有多少片段/多大的片段。这基本上就是Martin York提到的SGI绳索:一个字符串,其中append形成了片段的链接列表而不是连续的缓冲区,因此适用于更长的值。

更新(由于我仍然看到偶尔的点赞),C++17引入了另一个选择:std::string_view,它在许多函数签名中替换了std::string,是对字符数据的非拥有引用。它可以从std::string隐式转换,但也可以从其他地方拥有的连续数据显式构造,避免了std::string强制执行的不必要复制。


我认为你的解决方案是最好的方法(更改处理代码),但不幸的是,在这种情况下这不是一个选项。 - Akusete
1
有没有一种标准指定的方法来实现缓冲区重用?我不想依赖于特定平台上的实现。 - Akusete
除非处理代码是不使用迭代器和字符串,只使用普通的 char* + size 的库函数。 - SasQ

20

第一步是否可以使用 C++ 字符串?如果您使用 string::reserve(size_t),您可以分配足够大的缓冲区来防止在附加较小字符串时多次进行堆分配,然后您可以在所有剩余的步骤中都使用同一个 C++ 字符串。

有关 reserve 函数的更多信息,请参见此链接


1
@Akusete @e.James:(是的,我知道已经过去2.5年了)当我阅读标准的相关部分时,发现clear()是基于erase()定义的,而erase()并不明确指定调用erase()后正在使用的内存块是否与调用之前使用的相同,也不指定capacity()保持不变。例如,参见21.3.3和21.3.5.5。合理的实现当然可以这样运作,但请确保将其视为有用的优化,而不是正确程序功能所必需的。 - Nicholas Knight
@Nicholas Knight:说得好。一如既往,标准程序是在测量并确保必要性之后才进行优化。我想知道我在08年是否已经知道这一点了?:) - e.James
@jons34yp,提醒一下,你的编辑引起了一些关注。不用担心,但现在它们正在meta上讨论http://meta.stackexchange.com/questions/194788/links-being-changed-to-cppreference-com - Bart
@Akusete 直接通过函数成员更改字符串缓冲区大小的唯一方法是使用 std::string::shrink_to_fit。std::string::clear 只将字符串的大小(而不是缓冲区)设置为 0。 - TheArchitect
std::string::clear 的实现可以对底层缓冲区执行任何操作,包括释放它。 - codesniffer
显示剩余3条评论

7
为了处理非常大的字符串,SGI 在其 STL 中提供了 Rope 类。
虽然不是标准库,但可能很有用。
http://www.sgi.com/tech/stl/Rope.html
显然,Rope 将出现在下一个标准版本中 :-)
请注意开发人员的玩笑,Rope 是一个大字符串。(哈哈) :-)

6
这是一个侧面思考的答案,不直接回答问题,而是“围绕”它进行思考。可能有用,也可能没有...
对std::string进行只读处理并不需要非常复杂的std::string功能子集。是否有可能在执行所有std::string处理的代码上进行搜索/替换,以便使用其他类型代替?从一个空类开始:
class lightweight_string { };
然后将所有std::string引用替换为lightweight_string。进行编译以找出需要在lightweight_string上执行哪些操作,使其作为一种即插即用的替代品。然后您可以根据自己的需求使实现工作。

2

每个迭代是否足够独立,以至于您可以在每个迭代中使用相同的std :: string?希望您的std :: string实现足够智能,如果您将const char *分配给它,而先前已将其用于其他用途,则会重新使用内存。

将char *分配到std :: string中必须始终至少复制数据。内存管理是使用std :: string的主要原因之一,因此您将无法覆盖它。


0
在这种情况下,直接处理char*可能比将其分配给std::string更好。

3
是的,虽然输入(C char *)和输出(std :: string)不在我的控制范围内,但它仍然可以实现。 - Akusete

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接