【问题标题】:initializing std::string from char* without copy从 char* 初始化 std::string 而不复制
【发布时间】:2010-09-26 13:34:18
【问题描述】:

我有一种情况,我需要处理大量(许多 GB)的数据:

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

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

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

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

或者,我可以使用字符串流或类似的东西来重新使用大缓冲区吗?

编辑:感谢您的回答,为了清楚起见,我认为修改后的问题是:

如何有效地构建(通过多个附加)stl C++ 字符串。如果在一个循环中执行这个动作,每个循环都是完全独立的,我怎样才能重新使用这个分配的空间。

【问题讨论】:

    标签: c++ string memory-management stl


    【解决方案1】:

    如果不复制数据,您实际上无法形成 std​​::string。一个字符串流可能会重复使用一遍又一遍的内存(尽管我认为标准对它是否真的必须这样做保持沉默),但它仍然无法避免复制。

    解决此类问题的常用方法是编写处理步骤 3 中的数据的代码,以使用开始/结束迭代器对;然后它可以轻松地处理一个 std::string、一个字符向量、一对原始指针等。与传递它像 std::string 这样的容器类型不同,它不再知道或关心内存是如何分配的,因为它仍然属于调用者。将这个想法带到其合乎逻辑的结论是boost::range,它添加了所有重载的构造函数,仍然让调用者只传递一个字符串/向量/列表/任何类型的容器,带有 .begin() 和 .end(),或单独的迭代器.

    编写处理代码以在任意迭代器范围上工作后,您甚至可以编写自定义迭代器(不像听起来那么难,基本上只是一个带有一些标准 typedef 和运算符 ++/*/=/ 的对象==/!= 重载以获得一个只进的迭代器),它负责每次到达它正在处理的片段的末尾时前进到下一个片段,跳过空格(我假设这就是你的意思)修剪)。你根本不必连续组装整个字符串。这是否会获胜取决于您拥有多少碎片/多大的碎片。这本质上就是 Martin York 提到的 SGI 绳索:一个字符串,其中 append 形成一个片段的链表,而不是一个连续的缓冲区,因此适用于更长的值。


    更新(因为我仍然偶尔看到对此答案的支持):

    C++17 引入了另一种选择:std::string_view,它在许多函数签名中替换了 std::string,是对字符数据的非拥有引用。它可以从 std::string 隐式转换,但也可以从其他地方拥有的连续数据显式构造,避免 std::string 强加的不必要的复制。

    【讨论】:

    • 我认为您的解决方案是最好的方法(更改处理代码),不幸的是在这种情况下它不是一个选择。
    • 是否有标准的指定方法来实现缓冲区重用?我只是不想依赖特定平台上的实现。
    • 除非处理代码是一个不使用迭代器或字符串的库函数,只是一个普通的旧char* + size。
    【解决方案2】:

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

    有关reserve 函数的更多信息,请参阅this link

    【讨论】:

    • 您的解决方案听起来像我所追求的,但是,当您说“您可以使用相同的 C++ 字符串”时,您的意思是使用 clear(),然后继续构建下一个字符串吗?跨度>
    • 这个解决方案是我目前采用的最简单的解决方案,尽管我不确定标准是否真的指定了重用(尽管它似乎对我的实现有效)
    • 使用 clear() 应该可以。据我所知, clear() 不会释放字符串使用的内存,因此不会影响 reserve() 分配的空间。
    • @Akusete @e.James:(是的,我知道是 2.5 年后)当我阅读标准的相关部分时,clear() 是根据 erase() 和 erase( ) 具体规定调用erase()之后使用的内存块是否与调用erase()之前使用之前的内存块相同,也没有它规定容量()保持不变。参见例如21.3.3 和 21.3.5.5。合理的实现当然可以以这种方式运行,但请务必将其仅视为一种有用的优化——而不是正常程序运行的必要条件。
    • @Nicholas Knight:公平点。与往常一样,标准程序是仅在进行测量并确保有必要之后进行优化。我想知道我是否在 08 年就知道这一点? :)
    【解决方案3】:

    为了帮助处理非常大的字符串,SGI 在其 STL 中添加了 Rope 类。
    非标准,但可能有用。

    http://www.sgi.com/tech/stl/Rope.html

    显然绳索在标准的下一个版本中:-)
    注意开发人员的笑话。绳子是一根大绳子。 (哈哈):-)

    【讨论】:

      【解决方案4】:

      这是一个横向思考的答案,不是直接解决问题,而是围绕它“思考”。可能有用,也可能没用……

      std::string 的只读处理实际上并不需要 std::string 功能的一个非常复杂的子集。您是否有可能对在 std::strings 上执行所有处理的代码进行搜索/替换,以便它采用其他类型?从一个空白类开始:

      类轻量级字符串 { };

      然后将所有 std::string 引用替换为轻量级字符串。执行编译以准确找出轻量级字符串上需要哪些操作才能充当直接替换。然后,您可以随心所欲地进行实施。

      【讨论】:

        【解决方案5】:

        每次迭代是否足够独立,以至于您可以为每次迭代使用相同的 std::string?人们希望您的 std::string 实现足够聪明,可以在以前用于其他用途时为其分配 const char * 来重用内存。

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

        【讨论】:

          【解决方案6】:

          在这种情况下,最好直接处理 char*,而不是将其分配给 std::string。

          【讨论】:

          • 是的,虽然输入(C char*'s)和输出(std::string)不在我的控制之下。
          猜你喜欢
          • 2018-06-04
          • 1970-01-01
          • 2016-02-04
          • 2017-04-11
          • 2017-03-06
          • 2018-01-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多