C++11 及更高版本中是否禁止使用 COW basic_string?
关于
”我是否正确 C++11 不承认基于 COW 的 std::string 实现?
是的。
关于
”如果有,这个限制是否在新标准的某处明确说明(在哪里)?
几乎直接,通过需要对字符串数据进行 O(n) 次物理复制的许多操作的恒定复杂性要求在 COW 实现中。
例如,对于成员函数
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()、@987654327 @、end() 或 rend() 使引用字符串项的引用、指针和迭代器无效,即可能导致 COW 数据复制。在 C++11 中删除了此支持。
C++11 失效规则也禁止 COW 吗?
在撰写本文时被选为解决方案的另一个答案中,该答案被大力支持,因此显然相信,它断言
” 对于 COW 字符串,调用非const operator[] 将需要制作副本(并使引用无效),这是 [C++ 上面的 [quoted] 段落所不允许的11 §21.4.1/6]。因此,在 C++11 中使用 COW 字符串不再合法。
该断言在两个主要方面是不正确和误导的:
-
它错误地表示只有非
const 项目访问者需要触发 COW 数据复制。
但const 项目访问器也需要触发数据复制,因为它们允许客户端代码形成引用或指针,(在 C++11 中)不允许以后通过可触发 COW 数据复制的操作使其无效。李>
-
它错误地认为复制 COW 数据会导致引用失效。
但在正确的实现中,COW 数据复制、取消共享字符串值是在任何引用无效之前完成的。
要了解basic_string 的正确 C++11 COW 实现如何工作,当忽略使此无效的 O(1) 要求时,请考虑一个字符串可以在所有权策略之间切换的实现。字符串实例以策略共享开始。启用此策略后,将没有外部项目引用。该实例可以转换为唯一策略,并且它必须在可能创建项目引用时这样做,例如调用.c_str()(至少如果这会产生指向内部缓冲区的指针)。在多个实例共享值所有权的一般情况下,这需要复制字符串数据。在转换为 Unique 策略之后,实例只能通过使所有引用无效的操作(例如分配)转换回 Sharable。
因此,虽然该答案的结论(即排除了 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 的 COW 实现。但他们确实排除了一种相当有效的不受限制的 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) 要求被忽略怎么办?
如果 C++11 的常量时间要求在例如operator[] 被忽略,那么 basic_string 的 COW 在技术上是可行的,但很难实现。
可以访问字符串内容而不引起 COW 数据复制的操作包括:
- 通过
+连接。
- 通过
<<输出。
- 使用
basic_string 作为标准库函数的参数。
后者是因为允许标准库依赖于特定于实现的知识和构造。
另外,一个实现可以提供各种非标准函数来访问字符串内容,而不会触发 COW 数据复制。
一个主要的复杂因素是,在 C++11 中,basic_string 项目访问必须触发数据复制(取消共享字符串数据),但要求不抛出,例如C++11 §21.4.5/3 “抛出: 什么都没有。”。所以它不能使用普通的动态分配来创建一个新的缓冲区来复制COW数据。解决这个问题的一种方法是使用一个特殊的堆,其中可以保留内存而不实际分配,然后为每个对字符串值的逻辑引用保留必要的数量。在这样的堆中保留和取消保留可以是常数时间,O(1),分配一个已经保留的数量可以是noexcept。为了符合标准的要求,对于这种方法,似乎每个不同的分配器都需要一个这样的特殊的基于预留的堆。
注意事项:
¹ const 项目访问器触发 COW 数据复制,因为它允许客户端代码获取指向数据的引用或指针,不允许由例如触发的后续数据复制使其无效。非const 项目访问者。