Saturday, August 31, 2013

Qt Container Efficiencies

Ever stumble onto something completely obvious? You know, it's there looking you in the face every day but you don't even think about it? Take for example Qt containers with operator[]:

QStringList myEntries(fillupContainerSomewhere());
for(int i=0; i<myEntries.size(); ++i)
{
     if (myEntries[i] == "Hello") 
    {
        // Do something.
    }
}

What's wrong with this seemingly innocuous code (other than using .size(), that's obvious). We are filling up a Qt Container called QStringList with some entries, iterating through and checking against another string. This is about as basic as it gets.

To know the answer one has to know a little about Qt-style containers. They are pretty optimized for copying. Like everything in Qt, they are implemented in terms of a d-pointer (or pimpl idiom or handle idiom). Qt is smart about their containers and copying the containers. Say we do the following:

QStringList myEntries(fillupContainerSomewhere());
QStringList copyOfMyEntries(myEntries);
QStringList copy2OfMyEntries(myEntries);

Qt is smart- it only keeps the contents of 1 list in memory under the hood. They can do this by copying the d-pointer around so that multiple QStringList objects point at the same data. There is a caveat- what if one of the QStringList's is modified? The conservative and correct approach to this problem is that the contents MUST be completely copied and changed. If we didn't, what happens when:

QStringList myEntries(fillupContainerSomewhere());
QStringList copyOfMyEntries(myEntries);
QStringList copy2OfMyEntries(myEntries);
copyOfMyEntries.removeAt(4);

If we didn't force copyOfMyEntries to copy then modify all of the QStringList objects would be modified! That was definitely not the author's intent. The QStringList::operator[] returns an l-value (and if you remember, l-value means it can be modified!). Even if you are only using operator[] to read the value it will assume a write is about to take place forcing QStringList to make a deep copy! Ouch! 

The lesson here is that if you are only reading a value from a QStringList it should be done using the QStringList::at method instead- this returns an r-value and does not cause a deep copy to occur. [1] The second link in the references is good, we use it at work a lot! [2]

Keep in mind that this behavior for containers is NOT the same as the standard containers! The operator[] in a standard container (std::vector) means that if an out of bounds situation occurs it will simply fail whereas using "at" will cause an exception to be thrown. C++11 has added "at" to standard map. It appears to have the same side effect that if an element that does not exist is accessed it will throw an "out_of_range" exception. [3] 

The moral of the story, standard containers do not behave like Qt containers. The main moral is that when using Qt containers, use "at" method to read.


REFERENCES