Search code examples
c++qtqt5.9shared-dataqmultimap

Why is this local QMultiMap detaching when modified?


To give some background: in my project I put a debug breakpoint inside QMap::detach_helper because I wanted to see if I could spot any occurrences when implicitly shared QMaps were detaching due to an oversight e.g. using find when constFind could have been used. I didn't expect to hit it very often because mostly I am passing containers by const reference (as a side note there is apparently a tool called "clazy" to find such things).

I was then looking at some internal Qt v5.9.3 code that triggered the detach. The stacktrace shows that we are detaching from first line of insertMulti function which is called here on contexts:

// return true if accepted (consumed)
bool QGestureManager::filterEvent(QWidget *receiver, QEvent *event)
{
    QMap<Qt::GestureType, int> types;
    QMultiMap<QObject *, Qt::GestureType> contexts;
    QWidget *w = receiver;
    typedef QMap<Qt::GestureType, Qt::GestureFlags>::const_iterator ContextIterator;
    if (!w->d_func()->gestureContext.isEmpty()) {
        for(ContextIterator it = w->d_func()->gestureContext.constBegin(),
            e = w->d_func()->gestureContext.constEnd(); it != e; ++it) {
            types.insert(it.key(), 0);
            contexts.insertMulti(w, it.key());
        }
    }
    // find all gesture contexts for the widget tree
    w = w->isWindow() ? 0 : w->parentWidget();
    while (w)
    {
        for (ContextIterator it = w->d_func()->gestureContext.constBegin(),
             e = w->d_func()->gestureContext.constEnd(); it != e; ++it) {
            if (!(it.value() & Qt::DontStartGestureOnChildren)) {
                if (!types.contains(it.key())) {
                    types.insert(it.key(), 0);
                    contexts.insertMulti(w, it.key()); // Why does this trigger a detach?
                }
            }
        }
        if (w->isWindow())
            break;
        w = w->parentWidget();
    }
    return contexts.isEmpty() ? false : filterEventThroughContexts(contexts, event);
}

Why would local QMultiMap contexts-- which was never copied-- become implicitly shared and need to detach?


My Theory

This may not be relevant but size of contexts was zero at that line.

My guess is that the detach was caused by some kind of an optimization related to empty maps, but I'm not certain. I did notice that I get far fewer hits by putting the debug breakpoint inside the part of QMap::detach_helper which only executes for non-empty maps (that is, inside conditional if (d->header.left))


Solution

  • Q(Multi)Map does not detach on every insert but only on the first one when the map is not yet initialized:

    QMultiMap<int, int> mm;
    mm.insert(42, 43);  // detach_helper is called because the container needs to be initialized
    mm.insert(43, 44);  // detach_helper is not called