My question is basically when to choose QVector and when to choose QList as your Qt container. What I already know:
QList is an array of void*.
In its normal operation, it news the elements on the heap and stores a pointer to them in the void* array. Like a linked list, that means that references (but, unlike linked lists, not iterators!) to elements contained in the list remain valid under all container modifications until the element is removed from the container again. Thus the name "list". This datastructure is called an array-list and is used in a lot of programming languages where every object is of reference type (say, Java). It is a very cache-unfriendly data structure, like all node-based containers.
But the resizing of the array-list can be factored into a type-independent helper class (QListData), which is supposed to save some executable code size. In my experiments, it's next to impossible to predict which of QList, QVector or std::vector produces the least executable code.
This would have been a good data type for the many Qt reference-like types such as QString, QByteArray, etc., which consist of nothing more than a pimpl pointer. For these types, QList gained an important optimisation: when the type is not larger than a pointer (and please note that this definition depends on the platform's pointer size - 32 or 64bits), instead of heap-allocating objects, the objects are stored in the void* slots directly.
This is only possible, though, if the type is trivially relocatable. That means it can be relocated in memory using memcpy. Relocation here means I take an object, memcpy it to another address and - crucially - not run the destructor of the old object.
And this is where things started to go wrong. Because unlike in Java, in C++ a reference to an object is its address. And while in the original QList, references were stable until the object was removed from the collection again, by putting them into the void* array this property no longer holds. This is no longer a "list" for all intents and purposes.
Things continued to go wrong, though, because they allowed types that are strictly smaller than a void* to be placed in a QList, too. But the memory management code expects elements of pointer size, so QList adds padding(!). That means that a QList on 64bit platforms looks like this:
[ | | | | | | | [ | | | | | | | [ ...
[b| padding [b| padding [b...
Instead of fitting 64 bools into a cache line, like QVector does, QList only manages 8.
Things went wrong out of any proportion when the docs started calling QList a good default container. It's not. The original STL states:
Vectoris the simplest of the STL container classes, and in many cases the most efficient.
Scott Meyer's Effective STL has several items that start with "Prefer std::vector over...".
What is true in general C++ is not suddenly wrong just because you're using Qt.
Qt 6 will fix that particular design mistake. In the meantime, use QVector or std::vector.