【问题标题】:std::string implementation and expression templatesstd::string 实现和表达式模板
【发布时间】:2012-12-10 05:54:46
【问题描述】:

似乎 std::string - 因为它不使用表达式模板 - 具有 O(n^2) 复杂性,而不是像连接这样的某些操作可能具有 O(n) 复杂性。当您必须插入许多元素时,与 std::stringstream 类相同。

我想了解这一点,至少如果有人能提供一些关于这一点的好链接,那就太好了。

【问题讨论】:

  • 除了表达式模板(我不知道),我不知道串联如何达到 O(n ^ 2)。就两个字符串的长度而言,最多是线性的。
  • 我相信他说的是重复连接,将5个字符串连接在一起将复制第一个字符串4次,第二个字符串3次,等等......
  • @KenBloom +1 水晶球版本 1.0 (:
  • 这个链接会有帮助。“std::string 是如何实现的?” stackoverflow.com/questions/1466073/…

标签: c++ string complexity-theory


【解决方案1】:

将多个字符串连接在一起在 C++ 中具有不同的复杂性,具体取决于它是如何完成的。我相信你想到的情况是:

string result = string("Hello, ") + username + "! " +
                "Welcome to " + software_product + ".";

连接 6 个字符串。第一个字符串被复制 5 次,第二个字符串被复制 4 次,以此类推。正如Leonid Volnitsky 在他的回答中指出的那样,这个 Θ(NM) 的确切界限,其中 M 是连接操作的数量,N 是被连接的字符串的总长度。当 M

表达式模板可以帮助加速这个用例,尽管它会导致 C++11 中的 autodecltype 类型推断以及 C++98 中的模板类型推断出现问题。所有这些都会推断出

的类型
auto result = string("Hello, ") + username + "! " +
              "Welcome to " + software_product + ".";

成为用于使表达式模板魔术发生的延迟评估字符串模板类型。表达式模板不是一个好主意的其他原因包括 Leonid Volnitsky 的关于减慢编译时间的回答。它可能还会增加编译后的二进制文件的大小。

相反,C++ 中还有其他解决方案可用于获得 Θ(N) 连接:

string result = "Hello, ";
result += username;
result += "! ";
result += "Welcome to ";
result += software_product;
result += ".";

在这个版本中,字符串被原地修改,虽然已经复制到result的数据有时需要重新复制,但C++字符串通常实现为dynamic arrays,它以指数方式分配新空间,因此每个新字符的插入需要分摊的常数时间,导致重复连接的整体 Θ(N) 行为。

以下是做同样事情的一种方式,几乎在一条线上。它在内部使用相同的原理,但也支持使用

stringstream result;
result << "Hello, " << username << "! " << "Welcome to " << software_product
       << ".";
// do something with result.str()

最后,C++ 标准库不包含此功能,但可以定义以下函数,其中包含一些字符串流魔术。该实现留给读者作为练习。

template <typename... Items>
std::string concat(std::string const& a, std::string const& b, Items&&... args)

然后您可以调用concat 在 O(N) 时间内在一行上重复连接:

string result = concat("Hello, ", username, "! ", "Welcome to ",
                       software_product, ".");

据推测,所有这些都是比通过创建表达式模板类型来搞乱类型推断更好的解决方案。

【讨论】:

  • 为什么第一个被复制了5次?你确定不是 4 次吗?
  • @Guillaume07:首先,"Hello, "string 构造函数复制,尽管我希望编译器能够优化以避免这种复制。然后在语句中执行的 5 个连接操作中的每一个中复制一次。第二个字符串被复制 4 次,为语句中跟随它的 4 个连接操作中的每一个复制一次。 (不必首先调用 string 构造函数)。
  • 哦,是的,当然我在你的例子中看到了'+'运算符的数量,数为 4,这就是我不明白的原因
【解决方案2】:

如果我们有字符串表达式NM 连接操作的总长度,那么复杂度应该是O(NM)

使用表达式模板可以加快速度。可能因为它们的复杂性,它没有完成。编译速度也会更慢——它需要 M 递归类型。

【讨论】:

    【解决方案3】:

    我很难相信串联会达到O(n^2),但这里有一些与您的问题相关的答案:

    C++ 标准没有指定实现细节,只有 在某些情况下指定复杂性要求。唯一的复杂性 对 std::string 操作的要求是 size()、max_size()、 operator[]、swap()、c_str() 和 data() 都是常数时间。这 其他任何事情的复杂性取决于任何人所做的选择 实现了你正在使用的库。

    见参考:C++ string::find complexity

    【讨论】:

      【解决方案4】:

      怎么可能是 O(N^2)?字符串连接是:

      • 必要时重新分配(恒定摊销时间);
      • 查找第一个字符串的结尾(恒定时间,因为它们是计数字符串);
      • 第二个字符串的字符复制 (O(N))。

      我看不出模板与此有什么关系。

      【讨论】:

      • 因为使用模板可以延迟重复连接过程的评估,并且只有在知道最终字符串的长度时才执行一次分配。
      • @Guillaume07:模板如何在实现这种优化时为您提供任何优势?此外,它不会改变渐近性能,因为重新分配需要在“恒定摊销时间”内执行(实际上,分配大小通常在每次重新分配时加倍,因此在几次重新分配后,它们变得越来越少 -目标大小在最坏的情况下以对数时间达到,主要由副本的 O(N) 决定。
      猜你喜欢
      • 2013-12-05
      • 1970-01-01
      • 2015-09-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-23
      相关资源
      最近更新 更多