This question contains some proposals for working around the problem, I would like to understand more in depth that exactly the problem is:
QList<QString> q;
for (QString &x: q) { .. }
const
, Qt makes a copy of the list and then iterates over that copy? This is not among the best, but would be bearable if the list is small (say 10-20 QString's).It is important to understand that copy-on-write (= implicit shared) classes externally behaves like "normal" classes that perform a deep copy of their data. They only postpone this (potentially) expensive copy operation as long as possible. A deep copy is made (=detaching), only if the following sequence occurs:
Only if the container is shared (by another copy on write instance of this list), a copy of the list will be made (as a non-const member is invoked on the list object). Note that the C++ range loop is just a short hand for a normal iterator based for loop (see [1] for the exact equivalence which depends on the exactly used C++ version):
for (QList<QString>::iterator& it = q.begin(); x != q.end(); ++it) { QString &x = *it; ... }
Note that the begin
method is a const member function if and only if the list q
itself is declared const. If you would write it in full yourself, you should use constBegin
and constEnd
instead.
So,
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) { .. }
doesn't perform any copy, as list q
isn't implicitly shared with another instance.
However,
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) { .. }
does make a copy of the data.
This is mostly a performance issue . Only if the list contain some special type with a weird copy constructor/operator , this may be not the case, but this would probably indicate a bad design. In rare cases, you may also encounter the Implicit sharing iterator problem , by detaching (ie deep copy) a list when an iterator is still active.
Therefore, it is good practice to avoid unneeded copies in all circumstances by writing:
QList<QString> q = ...; for (QString &x: qAsConst(q)) { .. }
or
const QList<QString> q = ...; for (QString &x: q) { .. }
Modifications in the loop aren't broken and work as expected , ie they behave as if the QList
doesn't use implicit sharing, but performs a deep copy during a copy constructor/operator. For example,
QList<QString> q; q.resize(10); QList<QString>& q2 = q; QList<QString> q3 = q; for (QString &x: q) {x = "TEST";}
q
and q2
are identical, all containing 10 times "TEST". q3
is a different list, containing 10 empty (null) strings.
Also check the Qt documentation itself about Implicit Sharing , which is used extensively by Qt. In modern C++, this performance optimization construct could be (partially) replaced by newly introduced move concept.
Every non-const function calls detach
, before actually modifying the data, f.ex. [2] :
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()); }
However, detach
only effectively detaches/deep copy the data when the list is actually shared [3] :
inline void detach() { if (d->ref.isShared()) detach_helper(); }
and isShared
is implemented as follows [4] :
bool isShared() const noexcept
{
int count = atomic.loadRelaxed();
return (count != 1) && (count != 0);
}
ie more than 1 copy (= another copy except for the object itself) exists.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.