我只是注意到,根据最新的C ++ ISO标准,string::pop_back
和string::erase
(名称二,可能还有其他)成员函数具有未指定的复杂性。将复杂性留给图书馆编码员的选择背后的原因是什么?实际上是否存在任何人都知道string::pop_back
具有非常数复杂性的实现?
答案 0 :(得分:18)
[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)其中m
是s
的长度(来自其角色特征)此处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
的基于绳索的实现在实践中没有竞争力。