C ++字符串函数 - 为什么它们通常具有未指定的复杂性?

时间:2013-10-06 21:53:42

标签: c++ string time-complexity language-lawyer

我只是注意到,根据最新的C ++ ISO标准,string::pop_backstring::erase(名称二,可能还有其他)成员函数具有未指定的复杂性。将复杂性留给图书馆编码员的选择背后的原因是什么?实际上是否存在任何人都知道string::pop_back具有非常数复杂性的实现?

2 个答案:

答案 0 :(得分:18)

TLDR;因为历史和时间的复杂性尚未提出

情况:

[basic.string]仅直接指定一些操作具有一定的复杂性:

  • size():O(1),因为C ++ 11
  • max_size():O(1),因为C ++ 11
  • shrink_to_fit():C ++ 17中的O(n)(尽管在C ++ 11中添加)
  • operator[]:O(1),因为C ++ 11
  • swap():O(1)
  • data():O(1),因为C ++ 11

间接指定了更多(我可能在这里遗漏了一些):

  • length():等于size()
  • empty():等于size()
  • at():等于operator[]
  • front():等于operator[]
  • back():等于operator[]
  • copy():O(n)(来自其性格特征)
  • assign():O(n)(来自其性格特征)
  • find()和变体:O(n)(来自其性格特征)
  • append(char* s):O(m)其中ms的长度(来自其角色特征)

此处data()的要求至关重要。在C ++ 11之前,字符串的底层存储是实现定义的。它可能是所有重要事项的链表,只要它可以在需要时转换为c风格的数组。在C ++ 11之后,底层实现依赖于仍然平台,但要求data()为O(1),但保证字符串的存储是连续的C风格数组(不再懒惰地复制你的链表)。在C ++ 17中,data被重载以返回您可以修改的非const字符数组,并且此类修改与使用operator[]执行相同操作相同,这进一步巩固了实现(FWIW存储仍然依赖于实现,但是没有办法满足时间复杂度要求)。

你可以看到C ++ 03唯一的性能要求是在swap,这很长一段时间反映了标准的哲学;更喜欢只指定对象的行为,并让实现负责性能保证。这为库实现者提供了更大的灵活性,可以根据他们认为适合的情况进行优化。

历史上发生了什么

当您深入了解添加复杂性措辞的提案(例如n2923: Specifying the complexity of size())时,您会发现这些复杂性要求会随着人们的考虑而逐渐增加。

哎呀,非const data()是在C ++ 17中添加的,因为之前没有提出(link to std discussion on the matter(实际上只是链接回答我们的朋友Howard Hinnant {{3 }}))

写入时复制

Int posted on StackOverflow,作者详细讨论了basic_string,引用了copy-on-write作为其设计背后的最初理念之一(尽管它并非相当到那儿)

  

另一方面,basic_string的当前规范旨在允许引用计数的字符串,其中写时复制(COW)是必不可少的。

n8215同意)。

如果标准允许写入时复制,则在标准中不进行性能保证是有意义的,因为写入字符串可能是O(1)或O(N)。我猜想O(N)是正确的,但这是一个可能会产生误导的松散界限。

事实上,DanielKrügler想知道Wikipedia, citing Scott Meyers中和你一样的事情,并引用了写作时的痕迹:

  

由于早期支持copy-on-write,我们发现C ++ 0x有以下不必要的限制:
  ...   2.缺少复杂性保证:data()c_str()只返回指向其内容的指针,保证为O(1)。这应该拼写出来。

所以它看起来像是允许实现者灵活性,支持写入时写入字符串的丢弃想法,以及缺乏人们思考它的混合物。

随意为缺少它们的basic_string函数提出时间复杂性。标准可能会更好: - )

答案 1 :(得分:1)

很久以前,有人认为您可以使用ropes来实施std::string。 SGI STL甚至包含一个rope template,其界面非常string

但事实证明人们使用了下标运算符和c_str()方法,而不是有效的连接,这就是为什么std::string的基于绳索的实现在实践中没有竞争力。