C++ 11中COW std :: string实现的合法性

acm*_*acm 114 c++ string stdstring copy-on-write c++11

据我所知,写时复制不是std::string在C++ 11中实现符合性的可行方法,但最近在讨论中我发现自己无法直接支持该语句.

我是否正确C++ 11不承认基于COW的实现std::string

如果是这样,这个限制是否在新标准(其中)的某处明确说明了?

或者这个限制是否暗示,因为新要求的综合影响std::string排除了基于COW的实施std::string.在这种情况下,我会对"C++ 11有效禁止基于COW的std::string实现" 的章节和样式推导感兴趣.

Dav*_*e S 116

这是不允许的,因为根据标准21.4.1 p6,只允许迭代器/引用的失效

- 作为任何标准库函数的参数,将非const basic_string作为参数引用.

- 调用非const成员函数,除了operator [],at,front,back,begin,rbegin,end和rend.

对于COW字符串,调用非const operator[]将需要复制(并使引用无效),上面的段落不允许这样做.因此,在C++ 11中拥有COW字符串已不再合法.

  • @ Cheersandhth.-Alf:如果允许COW,可以在下面看到逻辑:`std :: string a("something"); char&c1 = a [0]; std :: string b(a); char&c2 = a [1];`c1是对a的引用.然后你"复制"一个.然后,当你第二次尝试获取引用时,它必须复制一个非const引用,因为有两个字符串指向同一个缓冲区.这必须使第一次引用无效,并且违反上面引用的部分. (10认同)
  • @ Cheersandhth.-Alf,根据[this](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=21334#c47),至少GCC的COW实现*确实做了DaveS所说的.因此,至少该标准禁止使用COW. (9认同)
  • ** - 1**逻辑没有水.在COW复制时没有可以失效的引用或迭代器,进行复制的全部意义在于现在正在获取这样的引用或迭代器,因此需要进行复制.但可能仍然是C++ 11不允许COW实现. (8认同)
  • 一些理由:[N2534](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2534.html) (4认同)
  • @Alf:这个答案认为非常数`operator []`(1)必须复制,(2)它是非法的.你不同意这两点中的哪一点?看看你的第一条评论,似乎一个实现可以共享字符串,至少在这个要求下,直到访问它,但是读取和写入访问都需要取消共享.这是你的推理吗? (4认同)
  • @ Cheersandhth.-Alf,我已经添加了[我自己的答案](http://stackoverflow.com/a/29199733/981959),它只是扩展了上面Dave S的正确答案.也许如果你现在重新阅读我上面的评论,你会看到我要求你为你自己看似非凡的主张提供一些理由,例如_"另一方面,认为那个错误意味着正式的东西,那个人其他人声称,这是胡说八道."_(我自己认为你被认为是胡说八道,但是当我使用同一个词时,我就是被指控进行广告攻击和个人攻击的人!这是多么令人反感!) (4认同)
  • @BenVoigt:我添加了一个正确的答案.与我的预期相反(但正如我在此列表中的第一篇评论中所说的那样),由于新的非投掷要求,实际上几乎不可能为C++ 11及更高版本提供一致的重新计算实现. (2认同)

Jon*_*ely 45

通过这些问题的答案戴夫小号gbjbaanb正确的.(而Luc Danton也是正确的,虽然它更像是禁止COW字符串的副作用,而不是禁止它的原始规则.)

但为了澄清一些困惑,我将进一步阐述.各种评论链接到我在GCC bugzilla上的评论,其中给出了以下示例:

std::string s("str");
const char* p = s.data();
{
    std::string s2(s);
    (void) s[0];
}
std::cout << *p << '\n';  // p is dangling
Run Code Online (Sandbox Code Playgroud)

该示例的要点是说明为什么GCC的引用计数(COW)字符串在C++ 11中无效.C++ 11标准要求此代码正常工作.代码中的p任何内容都不允许在C++ 11中失效.

使用GCC的老引用计数的std::string实现,该代码是未定义行为,因为p 无效的,成为一个悬摆指针.(接下来会发生的是,它在s2构造时与它共享数据s,但是获取非const引用通过s[0]要求数据不被共享,s"写入时复制"也是如此,因为引用s[0]可能会被用来写入s,然后s2去超出范围,破坏指向的数组p).

C++ 03标准明确允许 21.3 [lib.basic.string] p5中的行为,它表示在调用data()第一次调用之后operator[]()可能使指针,引用和迭代器无效.所以GCC的COW字符串是一个有效的C++ 03实现.

C++ 11标准不再允许这种行为,因为没有调用operator[]()可能使指针,引用或迭代器无效,无论它们是否跟随调用data().

所以上面的例子必须在C++ 11 工作,但不能与libstdc ++的COW字符串一起使用,因此在C++ 11中不允许使用那种COW字符串.

  • 如果您几乎每次访问字符串都取消共享,那么您将失去共享的所有好处.对于标准库来说,COW实现必须是实用的**才能使用它作为`std :: string`,我真诚地怀疑你能否展示出符合C++ 11失效要求的有用,高性能的COW字符串.所以我坚持认为,在最后一刻添加的"noexcept"规范是禁止COW字符串的结果,而不是根本原因.N2668似乎非常清楚,为什么你继续否认委员会在那里概述的明确证据? (7认同)
  • 对`.data()`(以及指针,引用或迭代器的每次返回)的调用取消共享的实现不会遇到该问题.即,(不变)缓冲区在任何时候都是非共享的,或者在没有外部引用的情况下共享.我以为你原本打算将关于这个例子的评论作为一个非正式的错误报告 - 作为评论,非常抱歉误解了它!但是你可以通过考虑我在这里描述的这种实现来看到,当忽略'noexcept`要求时,这在C++ 11中工作得很好,这个例子并没有说正式的.如果你愿意,我可以提供代码. (3认同)
  • 只是拼凑了一些代码,现在我发现有 129 个 `basic_string` 成员函数,加上自由函数。抽象成本:使用 g++ 和 MSVC 时,这种即用型非优化新第零版本代码的速度要慢 50% 到 100%。它没有实现线程安全(我认为利用 `shared_ptr` 很容易)并且它足以支持出于计时目的对字典进行排序,但是模错误证明了允许引用计数 `basic_string` 的点,除了对于 C++ `noexcept` 要求。https://github.com/alfps/In-principle-demo-of-ref-counted-basic_string/ (2认同)

gbj*_*anb 19

它是,CoW是一种可以接受的制作更快字符串的机制......但......

它使多线程代码变慢(所有锁定以检查你是否是唯一一个在使用大量字符串时写入杀死性能的人).这是多年前CoW被杀的主要原因.

其他原因是[]操作员将返回字符串数据,而不会保护您覆盖其他人希望不变的字符串.这同样适用于c_str()data().

快速谷歌说,多线程基本上是它被有效禁止(没有明确)的原因.

提案说:

提案

我们建议安全地同时执行所有迭代器和元素访问操作.

即使在顺序代码中,我们也在提高操作的稳定性.

此更改实际上不允许写入时复制实现.

其次是

由于远离写时复制实现而导致的最大潜在性能损失是具有非常大的读取主要字符串的应用程序的内存消耗增加.但是,我们认为对于那些应用而言,绳索是更好的技术解决方案,并且建议考虑将绳索建议包含在库TR2中.

绳索是STLPort和SGIs STL的一部分.

  • 愚蠢的是没有包含std :: cow_string类,使用lock_buffer()等等.有很多次我知道线程不是问题.实际上,往往是这样. (4认同)
  • 操作员[]问题并不是真正的问题。const变体确实提供了保护,非const变体始终可以选择在那时执行CoW(或非常疯狂并设置页面错误来触发它)。 (2认同)

Luc*_*ton 5

从21.4.2 basic_string构造函数和赋值运算符[string.cons]

basic_string(const basic_string<charT,traits,Allocator>& str);

[...]

2 效果:构造一个类对象,basic_string如表64所示.[...]

表64帮助文档通过此(复制)构造函数构造对象后,this->data()具有以下值:

指向数组的已分配副本的第一个元素,其中第一个元素由str.data()指向

其他类似构造函数也有类似的要求.


Che*_*Alf 5

C++11 及更高版本中是否禁止使用 COW basic_string

\n\n

关于

\n\n
\n

C++11 不承认基于 COW 的实现,我这样说对吗std::string

\n
\n\n

是的。

\n\n

关于

\n\n
\n

如果是这样,这个限制是否在新标准的某个地方(哪里)明确规定?

\n
\n\n

几乎直接地,由于许多操作的复杂性恒定,这需要在 COW 实现中对字符串数据进行 O( n ) 物理复制。

\n\n

例如,对于成员函数

\n\n
auto operator[](size_type pos) const -> const_reference;\nauto operator[](size_type pos) -> reference;\n
Run Code Online (Sandbox Code Playgroud)\n\n

...在 COW 实现中,\xc2\xb9 都会触发字符串数据复制以取消共享字符串值,C++11 标准要求

\n\n C++11 \xc2\xa721.4.5/4 :\n\n
\n

复杂性:恒定的时间。

\n
\n\n

…这排除了此类数据复制,因此排除了 COW。

\n\n

C++03 支持 COW 实现,因为没有这些恒定的复杂性要求,并且在某些限制条件下,允许调用operator[]()at()begin()rbegin()end()、 或rend()使引用字符串项的引用、指针和迭代器无效,即可能导致COW 数据复制。C++11 中删除了此支持。

\n\n
\n\n

C++11 失效规则是否也禁止 COW?

\n\n

在撰写本文时被选为解决方案的另一个答案中,该答案得到了大力支持,因此显然被相信,它断言

\n\n
\n

对于 COW 字符串,调用 non-const operator[]需要制作副本(并使引用无效),这是上面 [C++11 \xc2\xa721.4.1/6] 的 [quoted] 段落所不允许的。因此,在 C++11 中使用 COW 字符串不再合法。

\n
\n\n

这种说法在两个主要方面是不正确和具有误导性的:

\n\n
    \n
  • 它错误地指示只有非const项目访问器需要触发 COW 数据复制。
    \n但const项目访问器也需要触发数据复制,因为它们允许客户端代码形成引用或指针(在 C++11 中)不允许稍后通过可以触发 COW 数据复制的操作使其失效。
  • \n
  • 它错误地假设 COW 数据复制会导致引用无效。
    \n但在正确的实现中,COW 数据复制、取消共享字符串值是在任何可以失效的引用之前完成的。
  • \n
\n\n

要了解正确的 C++11 COW 实现如何basic_string工作,当忽略使其无效的 O(1) 要求时,请考虑一个字符串可以在所有权策略之间切换的实现。字符串实例以策略 Sharable 开始。如果此策略处于活动状态,则不能有外部项目引用。实例可以转换为 Unique 策略,并且当可能创建项目引用时(例如调用.c_str()(至少如果生成指向内部缓冲区的指针),则必须这样做。在多个实例共享该值的所有权的一般情况下,这需要复制字符串数据。转换到 Unique 策略后,实例只能通过使所有引用无效的操作(例如分配)转换回 Sharable。

\n\n

因此,虽然该答案的结论(即排除了 COW 字符串)是正确的,但所提供的推理是不正确的并且具有强烈的误导性。

\n\n

我怀疑造成这种误解的原因是C++11的附录C中的一个非规范注释:

\n\n C++11 \xc2\xa7C.2.11 [diff.cpp03.strings],关于 \xc2\xa721.3: \n\n
\n

更改basic_string要求不再允许引用计数字符串
\n 理由:无效与引用计数字符串略有不同。此更改规范了本国际标准的行为(原文如此)。
\n 对原始功能的影响:有效的 C ++ 2003 代码在此国际标准中的执行方式可能不同

\n
\n\n

这里的基本原理解释了为什么决定删除 C++03 特殊 COW 支持的主要原因。这个理由,即为什么,并不是标准如何有效地禁止 COW 实施。该标准不允许通过 O(1) 要求使用 COW。

\n\n

简而言之,C++11 失效规则并不排除std::basic_string. 但他们确实排除了一种相当高效、不受限制的 C++03 风格的 COW 实现,例如至少一个 g++ 标准库实现中的实现。特殊的 C++03 COW 支持提高了实际效率,特别是使用const项目访问器,但代价是微妙、复杂的无效规则:

\n\n C++03 \xc2\xa721.3/5,其中包括“首次调用”COW 支持:\n\n
\n

引用序列元素的引用、指针和迭代器可能会因该对象basic_string的以下使用而失效: \n \xe2\x80\x94 作为非成员函数的参数(21.3.7.8)、(21.​​3. 7.9)和(21.3.7.9)。\n \xe2\x80\x94 作为 的参数。\n \xe2\x80\x94 调用和成员函数。\n \xe2\x80\x94 调用非成员函数,除了、、、、和。\n \xe2\x80\x94 除了返回迭代器的和形式之外,在上述任何使用之后,第一次调用非成员函数, , , , , 或。basic_string
swap()operator>>()getline()
basic_string::swap()
data()c_str()
constoperator[]()at()begin()rbegin()end()rend()
insert()erase()constoperator[]()at()begin()rbegin()end()rend()

\n
\n\n

这些规则是如此复杂和微妙,以至于我怀疑许多程序员(如果有的话)能否给出精确的总结。我不能。

\n\n
\n\n

如果忽略 O(1) 要求怎么办?

\n\n

operator[]如果忽略eg上的C++11恒定时间要求,那么COW forbasic_string在技术上是可行的,但难以实现。

\n\n

可以访问字符串内容而不引起 COW 数据复制的操作包括:

\n\n
    \n
  • 通过 串联+
  • \n
  • 输出通过<<.
  • \n
  • 使用 abasic_string作为标准库函数的参数。
  • \n
\n\n

后者是因为标准库允许依赖于特定于实现的知识和构造。

\n\n

此外,实现可以提供各种非标准函数来访问字符串内容,而不触发 COW 数据复制。

\n\n

一个主要的复杂因素是,在 C++11 中,basic_string项访问必须触发数据复制(取消共享字符串数据),但要求不抛出,例如 C++11 \xc2\xa721.4.5/3 “抛出: Nothing。 ”。因此它不能使用普通的动态分配来创建新的缓冲区来进行COW数据复制。解决这个问题的一种方法是使用特殊的堆,可以在其中保留内存而不实际分配内存,然后为每个对字符串值的逻辑引用保留必要的数量。在这样的堆中保留和取消保留可以是常数时间 O(1),并且分配已经保留的数量可以是noexcept。为了符合标准的要求,使用这种方法似乎每个不同的分配器都需要一个这样的特殊的基于保留的堆。

\n\n
\n\n

\n注意:
\n\xc2\xb9const项目访问器触发 COW 数据复制,因为它允许客户端代码获取数据的引用或指针,不允许通过例如非-const项目访问器。\n

\n

  • 您提出的 COW 字符串很有趣,但我不确定它有多么“有用”。COW 字符串的要点是仅在写入两个字符串时复制字符串数据。您建议的实现需要在发生任何用户定义的读取操作时进行复制。即使编译器*知道*它只是读取,它仍然必须复制。此外,复制 Unique 字符串将导致其字符串数据的副本(大概是可共享状态),这再次使得 COW 毫无意义。因此,如果没有复杂性保证,您可以编写...一个*非常蹩脚的* COW 字符串。 (7认同)
  • @JonathanWakely:(1)你的引用不是问题。问题是:“C++11 不承认基于 COW 的 std::string 实现,我这样说对吗?如果是这样,新标准中是否明确规定了这一限制(哪里)?” (2) 您认为,当忽略 O(1) 要求时,COW `std::string` 效率低下,这是您的观点。我不知道表演会是什么样,但我认为提出这个断言更多的是为了它的感觉,为了它所传达的氛围,而不是为了与这个答案有任何相关性。 (5认同)
  • “_您的示例是 C++11 实现不正确的一个很好的示例。它可能对于 C++03 是正确的。”_ 是的 **这就是示例的要点**。它显示了一个 COW 字符串,该字符串在 C++03 中是合法的,因为它不会违反旧的迭代器失效规则,但在 C++11 中是不合法的,因为它确实违反了新的迭代器失效规则。这也与我在上面评论中引用的说法相矛盾。 (3认同)
  • 如果你说_可共享_而不是_最初共享_,我就不会争论。说某些东西最初是共享的只是令人困惑。与自身共享?这个词不是这个意思。但我重复一遍:_你试图论证 C++11 迭代器失效规则不会禁止某些在实践中从未使用过的假设的 COW 字符串(并且会产生不可接受的性能),而它们肯定会禁止这种类型的 COW 字符串在实践中使用的,有点学术性和毫无意义。_ (3认同)
  • 如果调用 `s.c_str()` 时 `s` 尚未共享怎么办?您如何处理“c_str()”返回的指针的**稍后**失效?该字符串需要记住是否**曾经**获取过任何引用,即使它是唯一拥有的,并且永远不允许共享。这在现实世界中是不切实际的。 (2认同)
  • 不,它最初是唯一拥有的。那不是共享的。当引用计数超过 1 时,它就会被共享。如果您要重新定义 COW 的含义,与问题的内容不同,请在其他地方这样做。 (2认同)
  • @JonathanWakely:再次假设您的意思是您所写的,并且只是不完全理解事情,请考虑一个字符串可以在所有权策略之间切换的实现。COW 字符串以策略 Sharable 开头。它可以转换为 Unique 策略,并且在可能创建项目引用时必须这样做,例如调用“.c_str()”(至少如果这会生成指向内部缓冲区的指针)。这通常需要复制字符串数据。之后,它只能通过使所有引用无效的操作(例如赋值)转换为可共享。明白了吗? (2认同)
  • @JonathanWakely:不,关于 C++11 允许什么的问题不是关于 g++ 的旧 C++03 COW 字符串。那只是你的误导。问题是 C++11 允许什么或不允许什么。并且 C++11 不允许 COW 实现 `basic_string`。当一个基本事实正确的答案有其他部分“错误”时,是否可以对它投否决票?是的,当这个人拒绝解决问题时,我会因为不正确而投反对票。 (2认同)
  • 因此,虽然您在技术上正确地认为复杂性保证是阻止您编写“任何形式”的 COW 的原因,但实际上是 [basic.string]/5 阻止您编写任何真正“有用”形式的 COW 字符串。 (2认同)