众所周知,Qt 小部件使用隐式共享。所以我对 stl 容器也很感兴趣std::vector
,std::string
也使用隐式共享。
如果不是,为什么?因为它非常有用。
如果答案是肯定的,我们如何确定呢?我需要简单的 C++ stl 程序,它表明 stl 容器使用隐式共享。复制时它不会进行深度复制。
不,他们不能。当您尝试修改容器的内容,甚至在其上调用可变对象begin()
时,这将意味着潜在的写时复制,从而使对容器的所有引用和迭代器无效。这将是一个难以调试的情况,并且是被禁止的。
尽管std::string
从技术上讲它不是容器,但自 C++11 起仍然禁止进行写时复制:
引用 basic_string 序列元素的引用、指针和迭代器可能会因该 basic_string 对象的以下用途而失效:
...
— 调用非常量成员函数,除了 operator[]、at、front、back、begin、重新开始、结束和结束。
[字符串.要求]
...因为它非常有用。
呵呵,干嘛?通过引用传递几乎总能解决所有“性能问题”。原子引用计数在多处理器机器上本质上是不可扩展的。
除了其他人对容器中的 CoW 行为提出的反对意见之外,这里还有一些。这些都属于违反惯例的行为类别,因此会导致毫无戒心的开发人员出现奇怪的错误。
例外
允许 CoW 意味着容器上的无害突变操作可能会失败并出现异常,否则它们不会。这将是一个特殊的危险,operator[]
无论是在一个std::vector
或std::string
穿线
人们可能合理地期望能够复制构造一个容器,其明确目的是将其交给另一个线程,而不必担心此后的并发性。CoW 并非如此。
正如在类似问题中注意到的那样:
C++ 标准不禁止或强制写入时复制或任何其他实现细节
std::string
。只要满足语义和复杂性要求,实现就可以选择它喜欢的任何实现策略。
我认为,对于std::vector
此外,您可能对这个主题感兴趣:如何实现 std::string
STL 容器不使用隐式共享。它们总是具有纯值语义。
原因在于运行时性能:在多线程程序(可能但不一定在多核主机上运行)中,管理数据的锁定开销(例如引用计数、写入前复制时锁定)远远超过纯值副本的开销,后者具有根本没有特殊的线程含义。预计将遭受巨大复制的程序std::maps
实施显式共享以避免复制。
事实上,在 STL 的早期,std::string
确实使用了隐式共享。但是当第一个多核 CPU 出现时,它就被放弃了。