STL容器是否使用隐式共享?

10

众所周知,Qt窗口小部件使用隐式共享。那么我很想知道STL容器std::vectorstd::string是否也使用隐式共享。

如果没有,为什么?因为它非常有用。

如果答案是肯定的,我们如何确定呢?我需要一个简单的C++ STL程序,它可以显示STL容器使用隐式共享。在复制时不进行深度复制。


2
更常见的术语是写时复制,标准容器不能执行它。我会让其他人挖掘原因。 - GManNickG
1
我认为在C++11标准中,禁止使用string的引用计数实现,因为它们在多线程环境下表现不佳。对于C++03我不确定。 - Andy Prowl
可能是GNU STL string: is copy-on-write involved here?的重复问题。 - Barmar
可能不是重复的问题,因为那个问题没有涉及到向量。 - Nathan Monteleone
4个回答

12

不可以。当您尝试修改容器的内容,甚至调用它的可变 begin() 方法时,这可能会导致潜在的写时复制,从而使所有引用和迭代器失效。这将是一个难以调试的情况,因此被禁止。

尽管 std::string 在技术上不是容器,但自 C++11 起,仍禁止执行写时复制:

  

引用、指针和迭代器引用 basic_string 序列的元素可能会被以下使用 basic_string 对象的操作所使无效:
  ...
  — 调用非 const 的成员函数,除了 operator[]、at、front、back、begin、rbegin、end 和 rend。

[string.require]

  

...因为它非常有用。

嘿,有何用处?几乎总是通过引用传递解决所有的“性能问题”。原子引用计数在多处理器机器上本质上是不可扩展的。


2
it is very useful”这个注释需要放到Qt的内容中 - 其设计者似乎对有用的语言特性(如异常和RTTI)有所厌恶。 - marko
2
@Marko:是的。Qt仍然建立在早期90年代狭隘的面向对象概念之上...但它成为今天流行的GUI库并不意味着它是一个好的库,而只是说明我们还没有更好的库,或者这样的库还没有赶上(不幸的是,好东西经常就这样消失了)。 - Yakov Galka
2
别再寻找其他选择了,QtXML正是为解决构造函数不总是完全构造对象的问题而设计的,其中流行的惯用法是每次都检查错误标志。 - marko
2
@ybungalobill 你说得对。我也很愚蠢地假设,Qt的实现不会受到迭代器问题的影响。但事实上它确实有这个问题。唉,为了解决这个问题,如果源对象上有活动迭代器,就必须显式地detach()复制。我将不得不审查我所有答案中的代码,以确保我不会无意中让任何人暴露在这个问题下。 - Kuba hasn't forgotten Monica
@KubaOber 这个 detach() 方法是什么?我没有看到它,至少在 QList 的 Qt 5.4 文档中没有。据我所知,在最新的 Qt 中,迭代器总是指向创建它的副本,如果集合随后被修改,它将指向一个新的副本,但迭代器仍将指向原始副本。文档中不清楚的是,如果您对某个迭代器执行 *i = 5,它会指向新的副本吗? - Andy
显示剩余10条评论

5
除了其他人对容器中的CoW行为提出的反对意见之外,以下是一些更多的反对意见。 这些都属于违背惯例的行为类别,因此会导致开发者遇到奇怪的错误。 异常 允许CoW会导致对容器的无害突变操作在本来不应该失败时触发异常。这在使用std::vectorstd::stringoperator[]时尤其危险。 线程 人们可能会合理地期望能够复制构造一个容器,以便将其交给另一个线程处理,之后就不必再担心并发问题了。但是使用CoW就不是这样。

你对异常的看法是正确的,但是关于线程的问题则有所偏差。像Qt中适当实现的写时复制技术是完全线程安全的,只要容器实例仅从单个线程访问即可。内部使用共享表示的事实并不重要。需要时它将被安全地复制,保留迭代器。 - Kuba hasn't forgotten Monica

3

正如在类似问题中所注意到的那样:

C++标准并不禁止或规定std::string的写时复制或任何其他实现细节。只要符合语义和复杂性要求,实现可以选择任何实现策略。

我认为,对于std::vector也是如此。

此外,您可能会对这个主题感兴趣:std::string实现的方式是什么?


4
我认为在C++11中禁止了写时复制。 - Pubby
2
@KubaOber,[string.require]/5,s[0] = 'a' 不允许使指向 s 的指针、引用或迭代器失效,这意味着它在返回第一个 char 的引用之前不能进行复制。此外,将该段与 C++03 标准进行比较,后者规定了不同的规则,提到了首次调用非 const operator[]、非 const at() 等(特别是为了允许 COW 实现在非 const 引用“泄漏”时进行深复制)。 - Jonathan Wakely

2

STL容器不使用隐式共享。它们始终具有普通值语义。

原因是运行时性能:在多线程程序中(可能但不一定在多核主机上运行),管理数据的锁定开销(例如参考计数,在写入之前复制时锁定)远远超过了普通值复制的开销,后者根本没有特殊的线程含义。预计那些需要频繁复制大型std::maps的程序将实现显式共享以避免复制。

事实上,在STL的早期阶段,std::string确实使用隐式共享。但是当第一个多核CPU出现时,它被删除了。


你是在声称STL比多核处理器更早吗? - Toby Speight
是的!好的。有点。模糊。就是这样。 :-) 早期的STL是在多核处理成为主流、变得便宜和成为标准之前开发的(就像今天一样)。当时,多线程编程也有些奇特。(不幸的是,即使今天,它仍然是火箭科学。) - Johannes Overmann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接