为什么Qt的foreach会创建容器的副本?

8
文档只是简单地说明了这一点(即它会自动复制容器),但没有解释为什么:
Qt在进入foreach循环时自动复制容器。如果您在迭代时修改容器,则不会影响循环。(如果您不修改容器,则仍会发生复制,但由于隐式共享,复制容器非常快。)由于foreach创建容器的副本,因此使用非const引用变量不能修改原始容器。它只会影响副本,这可能不是您想要的。
对我来说,这看起来像是一种自我限制,使得Qt的foreach比它本可以更有用-现在您无法使用它来修改元素。
我听说boost的foreach和新的C++11 for (auto iter : array)不执行复制(尽管我不熟悉它们中的任何一个)。
那么这种复制背后的原理是什么?

1
修改的正确方式是使用迭代器模式。 - László Papp
1
QT喜欢COW,因此他们广泛使用它来消除自己脚下的陷阱。查看C++标准库/boost的迭代器失效规则,您可能会理解其优点。不过,无论您是否依赖它,都会产生惩罚。 - Deduplicator
可能是因为很多人喜欢在迭代容器时修改它,这会使迭代器失效。 - ratchet freak
1
sashoalm,"kind-of"的原因在这里,https://dev59.com/y1TTa4cB1Zd3GeqPow0H#IHninYgBc1ULPQZFEdlB - László Papp
通常在迭代容器时修改它是一个错误。只有在特定情况下才不会出错。如果您使用Qt容器,则复制非常便宜。如果您希望修改容器,应直接使用迭代器,并以安全的方式使用它们。 - Kuba hasn't forgotten Monica
1个回答

5
Qt开发者已经决定,应该在循环中修改容器(在信号处理程序、a.k.a.插槽等情况下)而不修改原始容器时,防止出现意外情况。
使用信号插槽机制跟踪是否有任何修改可能会很棘手。如果您用该容器成员发射信号,则基本上是由插槽来完成的。为了确保正确,您就需要始终进行外部复制。
另一个优点是,您可以将方法调用传递给第二个参数,而无需连续重新计算,因为将在第一次创建副本。如果您问我,这实际上是一项相当不错的功能,因为通常您希望遍历关联数组键或值,例如myHash.keys()或myHash.values()。
您可以认为boost也具有信号插槽机制。是的,在我看来,这只是另一种不同的方法。它们不必总是做同样的事情。:-)
不同的人对API、样式等有不同的喜好。毕竟,您手中有所有的工具,可以实现您计划处理的任何用例。
您还可以认为它可能并不是您想要的,而是您会进行显式副本。Boost或C++标准foreach都可以达到这一点。
在此不需要担心复制的性能问题,因为对于不进行修改的普通迭代,复制写(a.k.a.隐式共享)足够好了。它有一些性能开销,但是可以忽略不计。
对于这种用例,在Qt中使用正确的语义将是使用迭代器设计模式。Qt遵循“Java风格”在各个地方都有迭代器类。

1
我记得曾经浪费了很多时间,试图弄清楚为什么我在退出foreach循环后我的更改会立即消失。那是一个嵌套的foreach循环,我忘记了我使用的是值而不是常量引用。我想知道是否只有我被这种情况困扰过。 - sashoalm
1
顺便问一下,C++11中的新for关键字也不会复制,对吧? - sashoalm
我需要查阅标准,但从记忆中来看,我会说不行。 - László Papp
说实话,我很惊讶复制是一个有意识的选择。我一直以为这是由于技术问题或为了避免重新评估表达式的第二个术语(如果它是函数)。 - sashoalm

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