derM
derM

Reputation: 13701

Manage the lifetime of C++ QObjects passed to QML in Signals

TL;DR
How do I correctly pass information, wrapped as a QObject to QML in a signal that might be emitted with high frequency, reducing overhead, ensuring the object/reference outlives at least the execution of the connected slots?


I have a C++ QObject registered as QML type. This object has some signal

void someSignal(InformationQObject* someInformation)

in which I don't pass all the information in seperate parameters but in one object - similar to the signals found e.g. in the MouseArea whith e.g. the signal

void clicked(QQuickMouseEvent *mouse)

Now I am wondering about the right lifetime management of this someInformation.

So far, in my object, I have a member:

InformationQObject* m_lastInformation

and to send the signal I use:

void sendMySignal(/* possible params */)
{
    delete m_lastInformation
    m_lastInformation = new InformationQObject(/* right params here */)
    emit someSignal(m_lastInformation)
}

Now this seems wrong.

Reasons: If you look at the implementation of the QQuickMouseArea they do it differently. Seemingly they don't create a new object for each event but recycle the existing one, seemingly. I find it hard to follow all their sources but I think this comment from one of their files gives a good reason:

QQuickPointerEvent is used as a long-lived object to store data related to an event from a pointing device, such as a mouse, touch or tablet event, during event delivery. It also provides properties which may be used later to expose the event to QML, the same as is done with QQuickMouseEvent, QQuickTouchPoint, QQuickKeyEvent, etc. Since only one event can be delivered at a time, this class is effectively a singleton. We don't worry about the QObject overhead because the instances are long-lived: we don't dynamically create and destroy objects of this type for each event.

But this is where it gets to complicated for me to see through, how they do it. This comment is regarding a QQuickPointerEvent. There exists a QQuickPointerMouseEvent. In their signal they pass a QQuickMouseEvent*

The latter is a pointer to one of their members QQuickMouseEvent quickMouseEvent.

At some point, somehow, this pointer becomes invalid in QML

MouseArea {
    anchors.fill: parent
    property var firstEvent
    onClicked: {
        if (firstEvent === undefined) firstEvent = mouse
        console.log(mouse.x, mouse.y)
        console.log(firstEvent.x, firstEvent.y) // -> TypeError on second and consecutive clicks.
    }
}

So there must be some magic happening, that I don't understand.

Upvotes: 4

Views: 1002

Answers (1)

dtech
dtech

Reputation: 49329

You are opening a can of worms. QML lifetime management is broken in above-trivial scenarios, and the API doesn't really give you a meaningful way to walk around that. The solution for me has been to set the ownership to CPP and manually manage the object lifetime. Primitive I know, but the only solution to avoid deletion of objects still in use and actual hard crashes.

If mouse area recycled the same event object, it wouldn't become invalid on the subsequent click.

If your code reflects your actual usage scenario, I recommend you simply copy the individual event properties rather than attempting to store the actual event, either in dedicated properties, or as a JS object if you want to avoid overhead and don't need notifications. I tend to use arrays, and rely on the faster index access.

Another solution I can recommend is a Q_GADGET with a PIMPL - gadgets are limited by design so they cannot be passed as pointers, and they are always copied by value, but you can have the actual object only contain a pointer to the heavier data implementation, and only serve as an accessor and interface to access the data from QML. This way you can reuse the data stuff, with the actual object value being negligible, as it will essentially just be a pointer and involve no dynamic memory allocation whatsoever. You can additionally expose the actual data as an opaque object for the sake of copying that to other gadgets and use ref counting to manage the data lifetime.

Upvotes: 5

Related Questions