这个问题包含一些解决该问题的建议,我想更深入地了解问题到底是:
QList<QString> q;
for (QString &x: q) { .. }
const
,Qt 会制作一个
列表的副本,然后迭代该副本?这不在其中
最好的,但如果列表很小(比如 10-20
QString 的)。重要的是要理解写时复制(=隐式共享)类的外部行为类似于执行数据(深层)复制的“普通”类。他们只是尽可能推迟这个(可能)昂贵的复制操作。仅当发生以下序列时,才会进行(深层)复制(=分离):
仅当容器被共享时(通过此列表的写入实例的另一个副本),将创建列表的副本(当在列表对象上调用非常量成员时)。请注意,C++ 范围循环只是基于 for 循环的普通迭代器的简写(请参阅 [1] 了解确切的等效项,这取决于确切使用的 C++ 版本):
for (QList<QString>::iterator& it = q.begin(); x != q.end(); ++it)
{
QString &x = *it;
...
}
请注意,当且仅当列表
begin
本身声明为 const 时,q
方法才是 const 成员函数。如果您自己完整编写,则应该使用 constBegin
和 constEnd
来代替。
那么,
QList<QString> q;
q.resize(10);
QList<QString>& q2 = q; // holds a reference to the same list instance. Modifying q, also modifies q2.
for (QString &x: q) { .. }
不执行任何复制,因为列表
q
不与另一个实例隐式共享。
但是,
QList<QString> q;
q.resize(10);
QList<QString> q2 = q; // Copy-on-write: Now q and q2 are implicitly shared. Modifying q, doesn't modify q2. Currently, no copy is made yet.
for (QString &x: q) { .. }
确实复制了数据。
这主要是一个性能问题。仅当列表包含某种带有“奇怪的复制构造函数/运算符”的特殊类型时,情况可能并非如此,但这可能表明设计不好。在极少数情况下,您还可能会遇到“隐式共享迭代器问题”,即在迭代器仍处于活动状态时分离(即深层复制)列表。
因此,最好的做法是通过编写以下内容来避免在所有情况下不需要的副本:
QList<QString> q = ...;
for (QString &x: qAsConst(q)) { .. }
或
const QList<QString> q = ...;
for (QString &x: q) { .. }
循环中的修改不会被破坏并按预期工作
QList
不使用隐式共享,而是在复制构造函数/运算符期间执行深层复制一样。例如,
QList<QString> q;
q.resize(10);
QList<QString>& q2 = q;
QList<QString> q3 = q;
for (QString &x: q) {x = "TEST";}
q
和
q2
相同,都包含10次“TEST”。
q3
是一个不同的列表,包含 10 个空 (null) 字符串。
另请检查 Qt 文档本身有关 隐式共享 通过检查源代码提高理解 每个非常量函数在实际修改数据之前都会调用
detach
:
inline iterator begin() { detach(); return reinterpret_cast<Node *>(p.begin()); }
inline const_iterator begin() const noexcept { return reinterpret_cast<Node *>(p.begin()); }
inline const_iterator constBegin() const noexcept { return reinterpret_cast<Node *>(p.begin()); }
但是,detach
仅在列表实际共享时才有效地分离/深度复制数据[3]
:
inline void detach() { if (d->ref.isShared()) detach_helper(); }
和 isShared
的实现如下[4]
:
bool isShared() const noexcept
{
int count = atomic.loadRelaxed();
return (count != 1) && (count != 0);
}
即存在超过 1 个副本(= 除对象本身之外的另一个副本)。