C++11及以后版本中是否禁止了基于COW的std::string
实现?
关于此问题:
“我是否正确,C++11不支持使用COW实现std::string
?
是的。
关于此问题:
“如果是这样,这种限制在新标准中是否有明确说明(在哪里)?
几乎直接地说明了。因为一些操作需要具有常数时间复杂度,并且在COW实现中需要O(n)物理拷贝字符串数据。
例如,对于成员函数:
auto operator[](size_type pos) const -> const_reference;
auto operator[](size_type pos) -> reference;
在COW实现中,这将会触发字符串数据的复制以取消共享字符串值。C++11标准要求此操作时间复杂度为常数级别(constant time)。
C++03通过不具备常数时间复杂度的要求,并在特定限制条件下允许调用operator[]()、at()、begin()、rbegin()、end()或rend()来使字符串项的引用、指针和迭代器失效,即可能导致COW数据复制来支持COW实现。但在C++11中,这种支持被移除了。
另外,在C++11中,即使是const item accessors也需要触发数据复制,因为它们允许客户端代码形成引用或指针,而这些引用或指针不能通过可以触发COW数据复制的操作后再次失效。
正确的C++11 COW实现应在任何可能失效引用之前进行COW数据复制转换,使其不再共享字符串值。一个实例开始时拥有Sharable策略,然后可以切换到Unique策略来独占值。在多个实例共享所有权的情况下,这就需要复制字符串数据。在切换回Sharable策略之前,需要进行一些使引用失效的操作,例如赋值。
因此,该答案的结论正确,即排除了COW字符串,但所提供的推理是不正确和极其误导的。我怀疑这种误解的原因是C++11附录C中的一个非规范性注释:
C++11 §C.2.11 [diff.cpp03.strings]关于§21.3:
“更改”:basic_string要求不再允许使用引用计数字符串
“基本原理”:引用计数字符串的失效略有不同。这个改变为这个国际标准规范了行为。
“对原始功能的影响”:有效的C++ 2003代码在这个国际标准下可能会执行不同的操作
这里的基本原理解释了为什么要删除C++03特殊的COW支持。这个基本原理(why)并不是标准有效地禁止COW实现的方法(how)。标准通过O(1)的要求禁止了COW。
简而言之,C++11的失效规则并没有排除使用COW实现std::basic_string,但它们确实排除了像g ++标准库实现中至少一个C++03风格的COW实现那样合理高效且没有限制。特殊的C++03 COW支持允许实现实际效率,尤其是使用const项访问器,但代价是复杂的失效规则。
这些规则非常复杂微妙,我怀疑许多程序员都无法给出精确的摘要,包括我自己。
如果不考虑O(1)的要求呢?
如果忽略了C ++11对例如operator[]
的恒定时间要求,则对于basic_string
而言,COW在技术上是可行的,但难以实现。
可以在不产生COW数据复制的情况下访问字符串内容的操作包括:
- 通过
+
进行连接。
- 通过
<<
进行输出。
- 将
basic_string
用作标准库函数的参数。
后者是因为标准库允许依赖于实现特定的知识和构造。
此外,实现可以提供各种非标准函数来访问字符串内容而不触发COW数据复制。
主要的复杂因素是,在C ++11中,项目访问必须触发数据复制(取消共享字符串数据),但需要不抛出异常,例如,C ++11 §21.4.5 /3“ Throws: Nothing。”。因此,它不能使用普通动态分配来创建新缓冲区以进行COW数据复制。解决这个问题的一种方法是使用一个特殊的堆,其中内存可以保留而不实际分配,然后为每个逻辑引用字符串值保留所需数量。在这样的堆中保留和取消保留可以是恒定时间O(1),在已经保留了所需的数量之后,分配可以noexcept
。为了符合标准的要求,使用这种方法似乎需要为每个不同的分配器提供一个基于预留的特殊堆。
注:
¹由于它允许客户端代码获取对数据的引用或指针,并且不允许通过稍后由非const
项目访问触发的数据复制使其无效,因此const
项目访问器会触发COW数据复制。