basic_string
C++11 及更高版本是否禁止使用COW ?
关于
” 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 标准要求
C++11 §21.4.5/4:
” 复杂性:恒定的时间。
......这排除了这种数据复制,因此,COW。
C++03 支持 COW 实现,没有这些恒定的复杂性要求,并且在某些限制条件下,允许调用operator[]()
, at()
, begin()
, rbegin()
, end()
, 或rend()
使引用字符串项的引用、指针和迭代器无效,即可能导致牛数据复制。在 C++11 中删除了此支持。
C++11 失效规则是否也禁止 COW?
在另一个答案中,在撰写本文时被选为解决方案,并且受到高度支持,因此显然相信,它断言
”对于 COW 字符串,调用 non-const
operator[]
需要制作副本(并使引用无效),这是 [C++11 §21.4.1/6] 上面的 [quoted] 段落所不允许的。因此,在 C++11 中使用 COW 字符串不再合法。
该断言在两个主要方面是不正确和误导的:
- 它错误地表明只有非
const
项目访问者需要触发 COW 数据复制。
但const
项目访问器也需要触发数据复制,因为它们允许客户端代码形成引用或指针(在 C++11 中),以后不允许通过可触发 COW 数据复制的操作使其无效。
- 它错误地假设 COW 数据复制会导致引用失效。
但在正确的实现中,COW 数据复制、取消共享字符串值是在任何引用可能无效之前完成的。
要了解正确的 C++11 COW 实现如何basic_string
工作,当忽略使此无效的 O(1) 要求时,请考虑一个字符串可以在所有权策略之间切换的实现。字符串实例以策略共享开始。启用此策略后,将没有外部项目引用。实例可以转换为唯一策略,并且它必须在可能创建项目引用时这样做,例如通过调用.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 失效规则不排除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)、 (21. operator>>()
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[]
如果忽略对 eg 的 C++11 恒定时间要求,那么 COW forbasic_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
项目访问器触发的后续数据复制使其无效。