【问题标题】:What are the rules for a C++ object returned from a Q_INVOKABLE to be owned and collected by QML?从 Q_INVOKABLE 返回的 C++ 对象由 QML 拥有和收集的规则是什么?
【发布时间】:2018-08-07 14:49:24
【问题描述】:

我有一个 C++ 类,它在 QML 中作为产生其他类型的单例公开

qmlRegisterSingletonType<DomainManager>("my.pkg", 1, 0, "DomainManager", domain_provider);
qmlRegisterUncreatableType<Control>("my.pkg", 1, 0, "Control", "Get it fresh from DomainManager");

DomainManager 有一个功能

Q_INVOKABLE Control* controlWriter(QString partition);

根据http://doc.qt.io/qt-5/qtqml-cppintegration-data.html#data-ownership,从Q_INVOKABLE 返回的对象归QML 所有,应该由GC 删除。唯一的例外似乎是在返回的对象上设置了父级时,情况并非如此。

我有一个StackView,其中包含具有属性的面板:

property Control ctrl: DomainManager.controlWriter(dummy.name)

我已经验证,当我将面板从堆栈中弹出时,Component.onDestruction 会被调用,因此面板确实会被删除。

但是,我在 C++ 对象上放置了以下析构函数,并且在整个应用程序退出之前它不会被删除。

~Control() { qDebug() << "deleting control"; };

我发现摆脱Control 对象的唯一方法是在Component.onDestruction 中调用ctrl.destroy() 以手动释放它。

为什么 QML 不释放这个对象?

包含该属性的完整 QML 文件如下。 ctrl 不在此文件之外使用。

import QtQuick 2.11
import my.pkg 1.0

Image {
  id: dummy
  property string name
  property Control ctrl: DomainManager.controlWriter(dummy.name)

  source: "dummy.jpg"
  fillMode: Image.PreserveAspectFit

  Connections {
    target: gamepad
    onAxisLeftYChanged: {
      ctrl.id = dummy.name
      ctrl.x = gamepad.axisLeftY * 32767
      ctrl.yaw = gamepad.axisLeftX * 32767
      ctrl.publish()
    }
    onAxisLeftXChanged: {
      ctrl.id = dummy.name
      ctrl.x = gamepad.axisLeftY * 32767
      ctrl.yaw = gamepad.axisLeftX * 32767
      ctrl.publish()
    }
  }
  Component.onDestruction: {
    // not sure why this requires manual clean-up
    ctrl.destroy()
  }
}

【问题讨论】:

  • 在我的例子中 QML 确实删除了 Control 对象,在下面的链接中你可以找到我的测试项目github.com/eyllanesc/stackoverflow/tree/master/51729672
  • 当对象作为方法调用的返回值返回给 QML 时,如果没有剩余的 JavaScript 对它的引用并且它没有 QObject::,QML 将跟踪它并删除它parent()。 但看起来 Qt 在 QML 引擎删除之前不会释放对象。
  • @eyllanesc 我向您的代码提交了一个 PR,以证明当您将属性设置为 null 时,它不会清理对象。
  • @Pepijn 该问题是由于您丢失了指针的位置而引起的,QML 将尝试清理存储在 ctrl 中的指针,并且在您的情况下,您已将其设置为 null。因此,您现在有责任这样做。
  • @Pepijn 就像这样做:Control * ctrl = new Control; ctrl = nullptr; delete ctrl; 指针丢失,因为 delete 删除了存储在 ctrl 中的引用。 delete 删除指针所指的变量。

标签: c++ qt qml


【解决方案1】:

让我们来看看什么是 QML GC:

ma​​in.cpp

DomainManager *example = nullptr;

static QObject *domain_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
    Q_UNUSED(engine)
    Q_UNUSED(scriptEngine)
    return example;
}


int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    example = new DomainManager;

    qmlRegisterSingletonType<DomainManager>("my.pkg", 1, 0, "DomainManager", domain_provider);
    qmlRegisterUncreatableType<Control>("my.pkg", 1, 0, "Control", "Get it fresh from DomainManager");

    QQmlApplicationEngine engine;
    QObject::connect(example, &DomainManager::collectGarbage,
                     [&engine]() {
        engine.collectGarbage();
        qDebug("collectGarbage");
    });
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

QML

Window
{
    visible: true
    height: 640
    width: 480

    Component {
        id: test
        Item {
            property Control ctrl: DomainManager.controlWriter("test")
        }
    }

    Component.onCompleted: {
        var c = test.createObject()
        console.log("Control created")
        c.destroy()
        //DomainManager.collectGarbage()
        console.log("Window onCompleted")
    }
}

在上面的代码中,我添加了DomainManager的附加信号collectGarbage来演示GC。

对象归 JavaScript 所有。当对象返回到 QML 作为方法调用的返回值,QML 会跟踪并删除它 如果没有剩余的 JavaScript 引用它并且它没有 QObject::父()。

  1. 上面的输出:
qml: Control created 
qml: Window onCompleted
  1. 然后取消注释DomainManager.collectGarbage() 告诉QML 引擎collectGarbage。 输出将是:
qml: Control created
collectGarbage
qml: Window onCompleted
deleting control
  1. control 对象的所有权更改为 Cpp QQmlEngine::setObjectOwnership(control, QQmlEngine::CppOwnership); 输出:
qml: Control created
collectGarbage
qml: Window onCompleted

结论:

代码演示展示了如果我强制释放垃圾会发生什么。 GC 不是智能指针。当对象的引用计数变为零时,QML GC 将销毁从Q_INVOKABLE 函数返回的对象,除了CppOwnership。但不是马上。我认为它类似于 Java GC。

收集垃圾:

通常你不需要调用这个函数;垃圾收集器 当 QJSEngine 决定它是 这样做是明智的(即,当一定数量的新对象已经 创建)。但是,您可以调用此函数来显式请求 应该尽快执行垃圾收集。

【讨论】:

    猜你喜欢
    • 2010-10-22
    • 1970-01-01
    • 2012-07-17
    • 1970-01-01
    • 2021-02-27
    • 2016-07-29
    • 2011-04-26
    • 2020-04-16
    • 1970-01-01
    相关资源
    最近更新 更多