Some small questions about qt container detachment.
-
This code will corrupt immediately:
QList<QString> test { "a", "b", "c" }; for (QString &c : test) { test.emplaceBack("hello"); c = "c"; } qDebug() << test;But this code will run normally:
QList<QString> test { "a", "b", "c" }; for (QString &c : test) { c = "c"; } qDebug() << test;Question 1:
After detaching from the "test" container. Why I'm still able to access the deleted shared data? (see the second code snippet)Question 2:
After detaching, is that shared data becomes READ-ONLY instead of deleting immediately? (see the first code snippet)Question 3:
Why the code below does not crash:QSet<QString> test { "hello", "world" }; for (const QString &str : test) { qDebug() << str; test.insert("yes"); } qDebug() << test;if you call insert on "test", it will detach, and the old shared data ["hello", "world"] will be deleted and qDebug() << str will be accessing deleted thing and it should crash.
Also this one:
QList<QString> test { "hello", "world" }; for (const QString &str : test) { qDebug() << str; test.emplaceBack("he"); } qDebug() << test;We magically do not crash by accessing deleted list share data.
Could someone please save me from this annoying qt detachment and explain qt detachment in a more human-readable way (compare to the official qt documentation)?
-
This code will corrupt immediately:
QList<QString> test { "a", "b", "c" }; for (QString &c : test) { test.emplaceBack("hello"); c = "c"; } qDebug() << test;But this code will run normally:
QList<QString> test { "a", "b", "c" }; for (QString &c : test) { c = "c"; } qDebug() << test;Question 1:
After detaching from the "test" container. Why I'm still able to access the deleted shared data? (see the second code snippet)Question 2:
After detaching, is that shared data becomes READ-ONLY instead of deleting immediately? (see the first code snippet)Question 3:
Why the code below does not crash:QSet<QString> test { "hello", "world" }; for (const QString &str : test) { qDebug() << str; test.insert("yes"); } qDebug() << test;if you call insert on "test", it will detach, and the old shared data ["hello", "world"] will be deleted and qDebug() << str will be accessing deleted thing and it should crash.
Also this one:
QList<QString> test { "hello", "world" }; for (const QString &str : test) { qDebug() << str; test.emplaceBack("he"); } qDebug() << test;We magically do not crash by accessing deleted list share data.
Could someone please save me from this annoying qt detachment and explain qt detachment in a more human-readable way (compare to the official qt documentation)?
QStringis a container class which use implicit sharing, which means when you are creating copies of aQStringinstance, the contained string will not be copied but only a counter is incremented.
Only when the instance content is changed, the instance will be detached, which means the counter is decremented and a buffer is allocated to store the new string.
This way you can save memory and avoid unneeded copies, which will also speed up the application.In first code, in the for loop the
QListis modified, so the list reference will be detached, which means a new QList is created.In the second, the
cis detached as soon as you are modifying it.For more details, please take time to read this ==> https://doc.qt.io/qt-5/implicit-sharing.html
-
@PikaCat said in Some small questions about qt container detachment.:
Thanks and what about question 3?
Why should it crash?
Code 1 will crash because you are doing things wrong.
This code don't make sense.@KroMignon Because once the container is detached by emplacing a new object, accessing qDebug() << "world" on the next iteration is accessing deleted data.
-
@PikaCat said in Some small questions about qt container detachment.:
Thanks and what about question 3?
Why should it crash?
Code 1 will crash because you are doing things wrong.
This code don't make sense.@KroMignon the code also works normally if you changes the code order sightly:
QList<QString> test { "hello", "world" }; for (const QString &str : test) { test.emplaceBack("he"); qDebug() << str; } qDebug() << test;And it's apparently wrong because after detaching, the old container is deleted. So the QString object does not exist in the old container and you still want to get a reference of it and output the content of it.
-
@KroMignon the code also works normally if you changes the code order sightly:
QList<QString> test { "hello", "world" }; for (const QString &str : test) { test.emplaceBack("he"); qDebug() << str; } qDebug() << test;And it's apparently wrong because after detaching, the old container is deleted. So the QString object does not exist in the old container and you still want to get a reference of it and output the content of it.
@PikaCat said in Some small questions about qt container detachment.:
And it's apparently wrong because after detaching, the old container is deleted. So the QString object does not exist in the old container and you still want to get a reference of it and output the content of it.
You are responsible of your code, so you have to be consistant.
If you want to change a container instance during iteration, you have to use an appropriated way to iterate:for(int idx = 0; idx < test.length(); ++idx) { }or
QMutableListIterator<QString> iter(test); while(iter.hasNext()) { iter.next(); }If you are written mess code, then you can not expect it will work.
-
@PikaCat said in Some small questions about qt container detachment.:
And it's apparently wrong because after detaching, the old container is deleted. So the QString object does not exist in the old container and you still want to get a reference of it and output the content of it.
You are responsible of your code, so you have to be consistant.
If you want to change a container instance during iteration, you have to use an appropriated way to iterate:for(int idx = 0; idx < test.length(); ++idx) { }or
QMutableListIterator<QString> iter(test); while(iter.hasNext()) { iter.next(); }If you are written mess code, then you can not expect it will work.
@KroMignon I know that code should be like that if you want to modify the container during iteration. But my orginal question is : Why accessing like I did above will not make the program crash? I just don't understand why we can access a memory that is deleted.
It just magically feels like you apply a deep copy to the container and use the copied one to do the C++11 range loop. In that scenario, modifying the original one won't affect the loop.
But qt's documentation said that this will only happen if you are using qt foreach loop. In range loop, it will not happen. And if that's the case, how begin() and end() iterator in the C++11 range loop works?
see https://doc.qt.io/qt-6/foreach-keyword.html -
@KroMignon I know that code should be like that if you want to modify the container during iteration. But my orginal question is : Why accessing like I did above will not make the program crash? I just don't understand why we can access a memory that is deleted.
It just magically feels like you apply a deep copy to the container and use the copied one to do the C++11 range loop. In that scenario, modifying the original one won't affect the loop.
But qt's documentation said that this will only happen if you are using qt foreach loop. In range loop, it will not happen. And if that's the case, how begin() and end() iterator in the C++11 range loop works?
see https://doc.qt.io/qt-6/foreach-keyword.html@PikaCat said in Some small questions about qt container detachment.:
But my orginal question is : Why accessing like I did above will not make the program crash?
Because you are lucky ;)
This code crashes with Qt 5.12 and MSVC2015 in release for example, but not in debug build.It may crash, but it may work depending on operating system and compiler options.
-
@PikaCat said in Some small questions about qt container detachment.:
But my orginal question is : Why accessing like I did above will not make the program crash?
Because you are lucky ;)
This code crashes with Qt 5.12 and MSVC2015 in release for example, but not in debug build.It may crash, but it may work depending on operating system and compiler options.
@KroMignon Thanks. I finally understand the crash. One last question: After detaching, in the C++11 range loop, does the test.end() returns the new container's end iterator or the old one? Theoretically it should returns the new one. But why when it is not crash, it can still tell where is the end of this iteration by using the begin iterator of the old container and the end iterator of the new container. Which is not a normal iterator pair!
-
@KroMignon Thanks. I finally understand the crash. One last question: After detaching, in the C++11 range loop, does the test.end() returns the new container's end iterator or the old one? Theoretically it should returns the new one. But why when it is not crash, it can still tell where is the end of this iteration by using the begin iterator of the old container and the end iterator of the new container. Which is not a normal iterator pair!
@PikaCat said in Some small questions about qt container detachment.:
After detaching, Does the test.end() returns the new container's end iterator or the old one? Theoretically it should returns the new one. But why when it is not crash, it can still tell where is the end of this iteration by using the begin iterator of the old container and the end iterator of the new container. Which is not a normal iterator pair!
Don't understand the question :(
test.end()always returns current iterator end, problem is the detached copy which could point to an item which no more exits. -
@KroMignon Thanks. I finally understand the crash. One last question: After detaching, in the C++11 range loop, does the test.end() returns the new container's end iterator or the old one? Theoretically it should returns the new one. But why when it is not crash, it can still tell where is the end of this iteration by using the begin iterator of the old container and the end iterator of the new container. Which is not a normal iterator pair!
@PikaCat Please also take a look at this article https://marcmutz.wordpress.com/effective-qt/containers/
It is a bit dated, some things have changed (QList is now performs as well as QVector if I understood the changes correctly) but it still gives a nice overview.
-
@PikaCat said in Some small questions about qt container detachment.:
After detaching, Does the test.end() returns the new container's end iterator or the old one? Theoretically it should returns the new one. But why when it is not crash, it can still tell where is the end of this iteration by using the begin iterator of the old container and the end iterator of the new container. Which is not a normal iterator pair!
Don't understand the question :(
test.end()always returns current iterator end, problem is the detached copy which could point to an item which no more exits.@KroMignon Because C++11 range loop is implemented using iterators, So for(auto &a : b) is equivalent to for(auto iter = b.begin(): iter != b.end(): ++iter) auto&a = *iter;
After detaching, iter is the iterator of the old container, b.end() is end iterator of the newly created container. So how is it possible to loop like that when it is not crash. -
@PikaCat said in Some small questions about qt container detachment.:
After detaching, Does the test.end() returns the new container's end iterator or the old one? Theoretically it should returns the new one. But why when it is not crash, it can still tell where is the end of this iteration by using the begin iterator of the old container and the end iterator of the new container. Which is not a normal iterator pair!
Don't understand the question :(
test.end()always returns current iterator end, problem is the detached copy which could point to an item which no more exits.@KroMignon Well, I found the answer on stackoverflow https://stackoverflow.com/questions/29578219/range-based-for-loop-vs-regular-iterator-for-loop.
end() is evaluated only once in the begining of range for, not every time. So for(auto &a : b) is actually equivalent to for(auto iter = b.begin(), endIter = b.end();iter !=endIter; ++iter) auto&a = *iter;
That perfectly explains everything.
Thanks anyway:) appreciate that. -
@KroMignon Well, I found the answer on stackoverflow https://stackoverflow.com/questions/29578219/range-based-for-loop-vs-regular-iterator-for-loop.
end() is evaluated only once in the begining of range for, not every time. So for(auto &a : b) is actually equivalent to for(auto iter = b.begin(), endIter = b.end();iter !=endIter; ++iter) auto&a = *iter;
That perfectly explains everything.
Thanks anyway:) appreciate that.@PikaCat said in Some small questions about qt container detachment.:
I found the answer on stackoverflow https://stackoverflow.com/questions/29578219/range-based-for-loop-vs-regular-iterator-for-loop.
end() is evaluated only once in the begining of range forSorry, for me it was obvious :(
Thanks anyway:) appreciate that.
Your welcome.