Qt wiki will be updated on October 12th 2023 starting at 11:30 AM (EEST) and the maintenance will last around 2-3 hours. During the maintenance the site will be unavailable.

Iterators: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
 
No edit summary
Line 1: Line 1:
'''English''' [[QtIterators Bulgarian|Български]]
'''English''' [[QtIterators_Bulgarian|Български]]


Written By : Girish Ramakrishnan, ForwardBias Technologies
[[Category:QtInternals]]


=Overview=
[toc align_right="yes" depth="1"]


Qt’s container classes (QVector, QList, QHash etc) support two styles of iterators: <span class="caps">STL</span>-style and Java style. This article digs into the implementation of the iterators. For a tutorial on Qt’s container classes, read the [http://doc.trolltech.com/latest/containers.html Qt Container documentation] ''[doc.trolltech.com]''.
Written By : Girish Ramakrishnan, ForwardBias Technologies
 
=<span class="caps">STL</span> iterators=


<span class="caps">STL</span> iterators are extremely efficient and fast because they behave essentially like pointers. QVector&lt;T&gt;::iterator is just a typedef of T * and is a pointer to an entry inside the array that QVector internally uses. operator+, – etc is just pointer arithmetic and using * operator on the iterator is just dereferencing the pointer.
= Overview =


For containers like QList, the iterator is a pointer to a internal QList’s Node. Suffice to say that that arithmetic and dereferencing is very fast since they operate directly on the data.
Qt's container classes (QVector, QList, QHash etc) support two styles of iterators: STL-style and Java style. This article digs into the implementation of the iterators. For a tutorial on Qt's container classes, read the &quot;Qt Container documentation&amp;quot;:http://doc.trolltech.com/latest/containers.html.


Qt’s containers are implicitly shared – when you copy an object, only a pointer to the data is copied. When the object is modified, it first creates a deep copy of the data so that it does not affect the other objects. The process of creating a deep copy of the day is called ''detach''’
= STL iterators =


Since, <span class="caps">STL</span>-style iterators are conceptually just pointers, they make modification to the underlying data directly. As a consequence, non-const iterators detach when created using Container::begin() so that other implicitly shared instances do not get affected by the changes. To merely iterate through the container without modifying it, const-iterators should be used. When an iterator is created using Container::constBegin(), the container does not detach.
STL iterators are extremely efficient and fast because they behave essentially like pointers. QVector&amp;lt;T&amp;gt;::iterator is just a typedef of T * and is a pointer to an entry inside the array that QVector internally uses. operator+, - etc is just pointer arithmetic and using * operator on the iterator is just dereferencing the pointer.


The implementation of <span class="caps">STL</span> style iterators leads to some programming errors and pitfalls:<br />'''Problem 1'''. Any operation that changes the container’s internal data structure will most likely make existing iterators invalid. For example, QVector::append() might cause QVector to reallocate it’s internal array and thus any existing iterator is a dangling pointer. Whether an iterator becomes invalid or not depends on the container and is an implementation detail. For example, a QMap’s iterator remains valid even after the removal of an item as long as the removed item was not the one the iterator is pointing to. The same is true for QHash::erase() which does not rehash unlike QHash::remove(). In general, the thumb rule is to not use an iterator after the container has been changed.
For containers like QList, the iterator is a pointer to a internal QList's Node. Suffice to say that that arithmetic and dereferencing is very fast since they operate directly on the data.


To illustrate:<br />
Qt's containers are implicitly shared - when you copy an object, only a pointer to the data is copied. When the object is modified, it first creates a deep copy of the data so that it does not affect the other objects. The process of creating a deep copy of the day is called ''detach'''


'''Problem 2'''. Creating a copy of a container when a non-const iterator is active has unexpected side effects. <br />
Since, STL-style iterators are conceptually just pointers, they make modification to the underlying data directly. As a consequence, non-const iterators detach when created using Container::begin() so that other implicitly shared instances do not get affected by the changes. To merely iterate through the container without modifying it, const-iterators should be used. When an iterator is created using Container::constBegin(), the container does not detach.


Qt’s containers are implicitly shared and hence vector2 and vector1 share point to the same data. The iterator manipulates this data directly (since iterator is just a pointer). This is counter-intuitive to the nature of value based types which gives the guarantee that values never change behind their back.
The implementation of STL style iterators leads to some programming errors and pitfalls:<br />'''Problem 1'''. Any operation that changes the container's internal data structure will most likely make existing iterators invalid. For example, QVector::append() might cause QVector to reallocate it's internal array and thus any existing iterator is a dangling pointer. Whether an iterator becomes invalid or not depends on the container and is an implementation detail. For example, a QMap's iterator remains valid even after the removal of an item as long as the removed item was not the one the iterator is pointing to. The same is true for QHash::erase() which does not rehash unlike QHash::remove(). In general, the thumb rule is to not use an iterator after the container has been changed.


=Java style iterators=
To illustrate:<br /><code><br />QVector&amp;lt;int&amp;gt; vector1;<br />vector1 &lt;&lt; 1 &lt;&lt; 2 &lt;&lt; 3 &lt;&lt; 4;<br />QVector&amp;lt;int&amp;gt;::const_iterator it = vector1.constBegin();<br />vector1 &lt;&lt; 5 &lt;&lt; 6 &lt;&lt; 7 &lt;&lt; 8; // we change vector1<br />qDebug() &lt;&lt; *it; // oops! it is dangling pointer<br /></code>


The main purpose of Java style iterators (other than the coding style) is to solve the problems of <span class="caps">STL</span> iterators (discussed above). Internally, they are implemented using <span class="caps">STL</span> iterators. So, for the purpose of this discussion, it is safe to assume that Java Style iterators also hold pointers but still manage to avert the problems of <span class="caps">STL</span>-iterators.
'''Problem 2'''. Creating a copy of a container when a non-const iterator is active has unexpected side effects.<br /><code><br />QVector&amp;lt;int&amp;gt; vector1;<br />vector1 &lt;&lt; 1 &lt;&lt; 2 &lt;&lt; 3 &lt;&lt; 4;<br />QVector&amp;lt;int&amp;gt;::iterator it = vector1.begin();<br />QVector&amp;lt;int&amp;gt; vector2 = vector1; // vector2 now shares the data with vector1<br />*it = 10;<br />// not just vector1, but vector2 is now modified too!<br /></code>


'''Problem 1 Solution'''. const (non-mutable) Java style iterators solve the first problem by having the iterator hold a local ''copy'' of the container. Internally, the const Java style iterators are implemented using <span class="caps">STL</span> iterators created over this ''copy''. By keeping a copy, the const java iterator is safe from changes to the original object since Qt will detach when the original container is modified. This means that you can continue to iterate even after the original object is invalid. To illustrate:<br />
Qt's containers are implicitly shared and hence vector2 and vector1 share point to the same data. The iterator manipulates this data directly (since iterator is just a pointer). This is counter-intuitive to the nature of value based types which gives the guarantee that values never change behind their back.


'''Problem 2 Solution'''. The non-const (mutable) Java style iterators solve the second problem by making the container ''non-sharable''. A non-sharable container is one that does not share it’s data even with new objects that create a copy of it. To illustrate,<br />
= Java style iterators =


Thanks to non-sharability, problem 2 is averted – writing using the iterator of one object does not overwrite the data of some other object.<br />
The main purpose of Java style iterators (other than the coding style) is to solve the problems of STL iterators (discussed above). Internally, they are implemented using STL iterators. So, for the purpose of this discussion, it is safe to assume that Java Style iterators also hold pointers but still manage to avert the problems of STL-iterators.


=QT_STRICT_ITERATORS=
'''Problem 1 Solution'''. const (non-mutable) Java style iterators solve the first problem by having the iterator hold a local ''copy'' of the container. Internally, the const Java style iterators are implemented using STL iterators created over this ''copy''. By keeping a copy, the const java iterator is safe from changes to the original object since Qt will detach when the original container is modified. This means that you can continue to iterate even after the original object is invalid. To illustrate:<br /><code><br />QVector&amp;lt;int&amp;gt; vector1;<br />vector1 &lt;&lt; 1 &lt;&lt; 2 &lt;&lt; 3 &lt;&lt; 4;<br />QVectorIterator&amp;lt;int&amp;gt; it(vector1); // 'it' now holds a copy of vector1<br />vector1 &lt;&lt; 5 &lt;&lt; 6 &lt;&lt; 7 &lt;&lt; 8; // vector1 detaches and the local copy inside QVectorIterator is untouched<br />qDebug() &lt;&lt; it.value(); // safe, will print 1. we are only iterating over a copy of vector1, not vector1 itself<br /></code>


By default, it sometimes becomes possible to assign non-const iterators to const-iterators. Thinking of an iterator as a typedef of some pointer type, C++ allows assignment of a non-const pointer to a const pointer. To illustrate:<br />
'''Problem 2 Solution'''. The non-const (mutable) Java style iterators solve the second problem by making the container ''non-sharable''. A non-sharable container is one that does not share it's data even with new objects that create a copy of it. To illustrate,<br /><code><br />QVector v1;<br />v1 &lt;&lt; 1 &lt;&lt; 2 &lt;&lt; 2;<br />QVector v2 = v1; // v1 and v2 share the same data thanks to implicit sharing<br />v1.setSharable(false); // marks v1 as non-sharable (internal API) and makes the container detach (i.e deep copy)<br />QVector v3 = v1; // v3 gets it's own copy of data since v1 is 'non-sharable'<br /></code>


The above code works but ideally QMap::constFind() should be used since creation of a non-const <span class="caps">STL</span> iterator causes a detach (deep-copy). To fix errors such as above at compile time, QT_STRICT_ITERATORS can be defined. This makes Qt define the iterators differently so they don’t cast into each other.
Thanks to non-sharability, problem 2 is averted - writing using the iterator of one object does not overwrite the data of some other object.<br /><code><br />QVector&amp;lt;int&amp;gt; vector1;<br />vector1 &lt;&lt; 1 &lt;&lt; 2 &lt;&lt; 3 &lt;&lt; 4;<br />QMutableVectorIterator it(vector1); // the iterator holds a pointer to vector1 data and makes vector1 non-sharable<br />QVector&amp;lt;int&amp;gt; vector2 = vector1; // since vector1 is not sharable, vector2 get it's own copy<br />it.setValue(10); // only vector1 modified<br /></code>


===Categories:===
= QT_STRICT_ITERATORS =


* [[:Category:QtInternals|QtInternals]]
By default, it sometimes becomes possible to assign non-const iterators to const-iterators. Thinking of an iterator as a typedef of some pointer type, C++ allows assignment of a non-const pointer to a const pointer. To illustrate:<br /><code><br />QMap&amp;lt;QString, QString&amp;gt; map;<br />QMap&amp;lt;QString, QString&amp;gt;::const_iterator it = map.find(&quot;girish&amp;quot;); // code compiles and works fine but find() returns the non-const QMap::iterator that detaches!<br /></code>

Revision as of 10:03, 24 February 2015

English Български

[toc align_right="yes&quot; depth="1&quot;]

Written By : Girish Ramakrishnan, ForwardBias Technologies

Overview

Qt's container classes (QVector, QList, QHash etc) support two styles of iterators: STL-style and Java style. This article digs into the implementation of the iterators. For a tutorial on Qt's container classes, read the "Qt Container documentation&quot;:http://doc.trolltech.com/latest/containers.html.

STL iterators

STL iterators are extremely efficient and fast because they behave essentially like pointers. QVector&lt;T&gt;::iterator is just a typedef of T * and is a pointer to an entry inside the array that QVector internally uses. operator+, - etc is just pointer arithmetic and using * operator on the iterator is just dereferencing the pointer.

For containers like QList, the iterator is a pointer to a internal QList's Node. Suffice to say that that arithmetic and dereferencing is very fast since they operate directly on the data.

Qt's containers are implicitly shared - when you copy an object, only a pointer to the data is copied. When the object is modified, it first creates a deep copy of the data so that it does not affect the other objects. The process of creating a deep copy of the day is called detach'

Since, STL-style iterators are conceptually just pointers, they make modification to the underlying data directly. As a consequence, non-const iterators detach when created using Container::begin() so that other implicitly shared instances do not get affected by the changes. To merely iterate through the container without modifying it, const-iterators should be used. When an iterator is created using Container::constBegin(), the container does not detach.

The implementation of STL style iterators leads to some programming errors and pitfalls:
Problem 1. Any operation that changes the container's internal data structure will most likely make existing iterators invalid. For example, QVector::append() might cause QVector to reallocate it's internal array and thus any existing iterator is a dangling pointer. Whether an iterator becomes invalid or not depends on the container and is an implementation detail. For example, a QMap's iterator remains valid even after the removal of an item as long as the removed item was not the one the iterator is pointing to. The same is true for QHash::erase() which does not rehash unlike QHash::remove(). In general, the thumb rule is to not use an iterator after the container has been changed.

To illustrate:

<br />QVector&amp;lt;int&amp;gt; vector1;<br />vector1 &lt;&lt; 1 &lt;&lt; 2 &lt;&lt; 3 &lt;&lt; 4;<br />QVector&amp;lt;int&amp;gt;::const_iterator it = vector1.constBegin();<br />vector1 &lt;&lt; 5 &lt;&lt; 6 &lt;&lt; 7 &lt;&lt; 8; // we change vector1<br />qDebug() &lt;&lt; *it; // oops! it is dangling pointer<br />

Problem 2. Creating a copy of a container when a non-const iterator is active has unexpected side effects.

<br />QVector&amp;lt;int&amp;gt; vector1;<br />vector1 &lt;&lt; 1 &lt;&lt; 2 &lt;&lt; 3 &lt;&lt; 4;<br />QVector&amp;lt;int&amp;gt;::iterator it = vector1.begin();<br />QVector&amp;lt;int&amp;gt; vector2 = vector1; // vector2 now shares the data with vector1<br />*it = 10;<br />// not just vector1, but vector2 is now modified too!<br />

Qt's containers are implicitly shared and hence vector2 and vector1 share point to the same data. The iterator manipulates this data directly (since iterator is just a pointer). This is counter-intuitive to the nature of value based types which gives the guarantee that values never change behind their back.

Java style iterators

The main purpose of Java style iterators (other than the coding style) is to solve the problems of STL iterators (discussed above). Internally, they are implemented using STL iterators. So, for the purpose of this discussion, it is safe to assume that Java Style iterators also hold pointers but still manage to avert the problems of STL-iterators.

Problem 1 Solution. const (non-mutable) Java style iterators solve the first problem by having the iterator hold a local copy of the container. Internally, the const Java style iterators are implemented using STL iterators created over this copy. By keeping a copy, the const java iterator is safe from changes to the original object since Qt will detach when the original container is modified. This means that you can continue to iterate even after the original object is invalid. To illustrate:

<br />QVector&amp;lt;int&amp;gt; vector1;<br />vector1 &lt;&lt; 1 &lt;&lt; 2 &lt;&lt; 3 &lt;&lt; 4;<br />QVectorIterator&amp;lt;int&amp;gt; it(vector1); // 'it' now holds a copy of vector1<br />vector1 &lt;&lt; 5 &lt;&lt; 6 &lt;&lt; 7 &lt;&lt; 8; // vector1 detaches and the local copy inside QVectorIterator is untouched<br />qDebug() &lt;&lt; it.value(); // safe, will print 1. we are only iterating over a copy of vector1, not vector1 itself<br />

Problem 2 Solution. The non-const (mutable) Java style iterators solve the second problem by making the container non-sharable. A non-sharable container is one that does not share it's data even with new objects that create a copy of it. To illustrate,

<br />QVector v1;<br />v1 &lt;&lt; 1 &lt;&lt; 2 &lt;&lt; 2;<br />QVector v2 = v1; // v1 and v2 share the same data thanks to implicit sharing<br />v1.setSharable(false); // marks v1 as non-sharable (internal API) and makes the container detach (i.e deep copy)<br />QVector v3 = v1; // v3 gets it's own copy of data since v1 is 'non-sharable'<br />

Thanks to non-sharability, problem 2 is averted - writing using the iterator of one object does not overwrite the data of some other object.

<br />QVector&amp;lt;int&amp;gt; vector1;<br />vector1 &lt;&lt; 1 &lt;&lt; 2 &lt;&lt; 3 &lt;&lt; 4;<br />QMutableVectorIterator it(vector1); // the iterator holds a pointer to vector1 data and makes vector1 non-sharable<br />QVector&amp;lt;int&amp;gt; vector2 = vector1; // since vector1 is not sharable, vector2 get it's own copy<br />it.setValue(10); // only vector1 modified<br />

QT_STRICT_ITERATORS

By default, it sometimes becomes possible to assign non-const iterators to const-iterators. Thinking of an iterator as a typedef of some pointer type, C++ allows assignment of a non-const pointer to a const pointer. To illustrate:

<br />QMap&amp;lt;QString, QString&amp;gt; map;<br />QMap&amp;lt;QString, QString&amp;gt;::const_iterator it = map.find(&quot;girish&amp;quot;); // code compiles and works fine but find() returns the non-const QMap::iterator that detaches!<br />