管理在信号中传递给 QML 的 C++ QObject 的生命周期

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

TL;DR
我如何正确传递信息,包装为 QObject 到 QML 可能以高频发出的信号,减少开销,确保 object/reference 至少比连接插槽的执行时间长?


我有一个注册为 QML 类型的 C++ QObject。这个对象有一些信号

void someSignal(InformationQObject* someInformation)

其中我没有在单独的参数中传递所有信息,而是在一个对象中传递 - 类似于找到的信号,例如在 MouseArea 中,例如信号

void clicked(QQuickMouseEvent *mouse)

现在我想知道这个 someInformation 的正确生命周期管理。

到目前为止,在我的对象中,我有一个成员:

InformationQObject* m_lastInformation

并发送我使用的信号:

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

现在这似乎是错误的。

原因:如果您查看 QQuickMouseArea 的实施,他们的做法会有所不同。看起来他们并没有为每个事件创建一个新对象,而是回收了现有的对象。我发现很难追踪他们的所有消息来源,但我认为来自他们文件之一的评论给出了一个很好的理由:

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.

但这就是让我看穿他们如何做到这一点变得复杂的地方。此评论是关于 QQuickPointerEvent。存在一个QQuickPointerMouseEvent。在他们的信号中,他们传递了一个 QQuickMouseEvent*

后者是指向其中一个成员的指针QQuickMouseEvent quickMouseEvent

在某种程度上,这个指针在 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.
    }
}

所以一定是发生了一些我不明白的魔法。

你打开了一罐蠕虫。 QML lifetime management is broken in above-trivial scenarios,而 API 并没有真正为您提供一种有意义的方式来绕过它。我的解决方案是将所有权设置为 CPP 并手动管理对象生命周期。我知道这很原始,但这是避免删除仍在使用的对象和实际硬崩溃的唯一解决方案。

如果鼠标区域回收同一个事件对象,则不会在后续点击时失效。

如果您的代码反映了您的实际使用场景,我建议您简单地复制各个事件属性,而不是尝试存储实际事件,无论是在专用属性中,还是作为 JS 对象,如果您想避免开销和不不需要通知。我倾向于使用数组,并依赖于更快的索引访问。

我可以推荐的另一个解决方案是 Q_GADGET 带有 PIMPL - 小工具受设计限制,因此它们不能作为指针传递,它们总是按值复制,但您只能拥有实际对象包含指向较重数据实现的指针,并且仅用作从 QML 访问数据的访问器和接口。通过这种方式,您可以重用数据内容,实际对象值可以忽略不计,因为它本质上只是一个指针,不涉及任何动态内存分配。您还可以将实际数据公开为不透明对象,以便将其复制到其他小工具,并使用引用计数来管理数据生命周期。