0

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))

Patrick Parker
  • 4,863
  • 4
  • 19
  • 51
  • Since insertMulti() modifies the container, detach() must be called to make sure no other instance is referencing this container. – chehrlic Jun 19 '21 at 08:26
  • @chehrlic of course, but `detach_helper` is only called when the container is implicitly shared. otherwise the "detach()" is a no-op and doesn't actually detach from anything. – Patrick Parker Jun 19 '21 at 12:04
  • 1
    `called when the container is implicitly shared. ` - Qt containers are by default implicit shared so detach() has to be called on every (possible) write operation. – chehrlic Jun 19 '21 at 12:54
  • Right, I already realize that detach() has to be called on every possible write. I am referring to `detach_helper()` which should only be called if it is really sharing data with another QMap instance. See qmap header: https://code.woboq.org/qt5/qtbase/src/corelib/tools/qmap.h.html#_ZN4QMap6detachEv – Patrick Parker Jun 19 '21 at 17:47
  • I still don't see where detach_helper() is called except when the container is completely uninitialized (= the first time): `QMultiMap 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 ` – chehrlic Jun 19 '21 at 19:12
  • @chehrlic thanks, I suspected something along those lines. If you write it up as an answer I'll give you credit for that answer. – Patrick Parker Jun 19 '21 at 22:53

1 Answers1

0

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
chehrlic
  • 913
  • 1
  • 5
  • 5