basic_string
在C ++ 11及更高版本中是否禁止COW ?
关于
”我是正确的是C ++ 11不承认基于COW的实现std::string
?
是。
关于
”如果是,此限制是否在新标准的某个地方(where)中明确说明?
几乎直接地,对于许多操作的恒定复杂性的要求,这将需要在COW实现中对字符串数据进行O( n)物理复制。
例如,对于成员函数
auto operator[](size_type pos) const -> const_reference;
auto operator[](size_type pos) -> reference;
…在COW实现中,这将“触发字符串数据复制以取消共享字符串值”,C ++ 11标准要求
C ++ 11§21.4.5/ 4:
” 复杂度:常量时间。
……排除了此类数据复制,因此也禁止了COW。
C ++ 03通过支持COW实现不具有这些恒定的复杂性的要求,并且,通过在一定的限制条件,从而允许呼叫operator[]()
,at()
,begin()
,rbegin()
,end()
,或rend()
到无效引用指针和迭代参照串项目,即,以可能招致COW数据复制。此支持已在C ++ 11中删除。
是否通过C ++ 11无效规则也禁止COW?
在写本文时被选为解决方案的另一个答案中,该答案被强烈推崇,因此显然被认为是:
”对于一个COW字符串,在调用非const
operator[]
需要进行复印(和无效的参考文献),它是由[引述]段落[C ++ 11§21.4.1/ 6]上述禁止。因此,在C ++ 11中拥有COW字符串不再合法。
该断言是不正确的,并且在两个主要方面具有误导性:
- 它错误地指示只有非
const
项目访问者才需要触发COW数据复制。
但是const
项目访问器也需要触发数据复制,因为它们允许客户端代码形成引用或指针(在C ++ 11中),以后不允许通过触发COW数据复制的操作使它们无效。
- 它错误地假定COW数据复制会导致引用无效。
但是,在正确的实现方式中,取消共享字符串值的COW数据复制是在任何引用无效之前进行的。
要了解正确的C ++ 11 COW实现如何basic_string
工作,当使该无效的O(1)要求被忽略时,请考虑一个可以在所有权策略之间切换字符串的实现。字符串实例以策略Sharable开始。启用此策略后,将不会有任何外部项目引用。实例可以转换为唯一策略,并且在可能创建项目引用(例如调用)时.c_str()
(至少在生成指向内部缓冲区的指针的情况下),它必须这样做。在多个实例共享值所有权的一般情况下,这需要复制字符串数据。在过渡到“唯一”策略之后,实例只能通过使所有引用(例如分配)无效的操作过渡回“可共享”状态。
因此,尽管该答案得出的结论是排除了COW字符串,但它是正确的,但所提供的推理是不正确的,并且极易引起误解。
我怀疑造成这种误解的原因是C ++ 11的附件C中的非规范性注释:
C ++ 11§C.2.11[diff.cpp03.strings],关于§21.3:
更改:basic_string
需求不再允许引用计数的字符串
依据:无效与引用计数的字符串有所不同。此更改使该国际标准的行为规范化。
对原始功能的影响:有效的C ++ 2003代码在本国际标准中的执行方式可能有所不同
这里的基本原理解释了为什么决定删除C ++ 03特殊COW支持的主要原因。该理由(为什么)不是该标准如何有效地禁止COW实施。该标准不允许通过O(1)要求进行COW。
简而言之,C ++ 11无效规则不排除的COW实现std::basic_string
。但是他们确实排除了合理有效的不受限制的C ++ 03样式的COW实现,例如至少在g ++的一种标准库实现中。特殊的C ++ 03 COW支持const
以较低的,复杂的无效规则为代价,从而提高了实用效率,尤其是使用项目访问器时:
C ++ 03§21.3/ 5,其中包括“首次致电” COW支持:
”引用basic_string
序列元素的引用,指针和迭代器可能
由于该basic_string
对象的以下用法而无效:
—作为非成员函数swap()
(21.3.7.8),operator>>()
(21.3.7.9)和getline()
(21.3)的参数。 7.9)。
—作为的参数basic_string::swap()
。
—调用data()
和c_str()
成员函数。
-调用非const
成员函数,除了operator[]()
,at()
,begin()
,rbegin()
,end()
,和rend()
。
-之后任何上述用途以外的形式insert()
和erase()
其返回迭代器,所述第一呼叫到非const
成员函数operator[]()
,at()
,begin()
,rbegin()
,end()
或rend()
。
这些规则是如此复杂和微妙,以至于我怀疑许多程序员(如果有的话)能否给出准确的总结。我不能。
如果忽略O(1)要求怎么办?
如果operator[]
忽略例如C ++ 11的恒定时间要求,则COW for basic_string
在技术上可能可行,但难以实现。
无需访问COW数据即可访问字符串内容的操作包括:
- 通过串联
+
。
- 通过输出
<<
。
basic_string
对标准库函数使用as参数。
后者是因为允许标准库依赖于实现特定的知识和构造。
另外,一个实现可以提供各种非标准功能来访问字符串内容,而无需触发COW数据复制。
一个主要的复杂因素是,在C ++ 11中,basic_string
项目访问必须触发数据复制(取消共享字符串数据),但必须不抛出,例如C ++ 11§21.4.5/ 3“ Throws: Nothing。”。因此,它不能使用普通的动态分配为COW数据复制创建新的缓冲区。解决此问题的一种方法是使用特殊的堆,在其中可以保留内存而无需实际分配,然后为每个对字符串值的逻辑引用保留必需的数量。在这样的堆中保留和取消保留可以是恒定时间O(1),而分配一个已经保留的量可以是noexcept
。为了符合该标准的要求,使用这种方法似乎每个单独的分配器都需要一个这样的基于特殊保留的堆。
注意:
¹ const
项目访问器触发COW数据复制,因为它允许客户端代码获取对数据的引用或指针,例如,非const
项目访问器触发的更高版本的数据复制不允许该引用或指针无效。