【问题标题】:Qt5.6 QML, why are dynamic models destroyed after garbage collection?Qt5.6 QML,为什么动态模型在垃圾回收后会被销毁?
【发布时间】:2016-09-16 11:39:07
【问题描述】:

我的组件数量不定,所以我尝试为每个组件分配自己的model。在这个例子中,我只是创建了一个,但想法是一样的。

GC() 有点随机,所以在示例中,我在单击后强制 gc() 以清除问题。发生的情况是model 被破坏并变为空。之后点击方法就不能使用了。

main.qml:

import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.2

import com.example.qml 1.0

ApplicationWindow
{
    visible: true
    width: 640
    height: 480

    // builder of dynamic models
    ModelFactory { id: maker }

    Column
    {
        anchors.fill: parent
        Repeater
        {
            // create dynamic model
            model: maker.makeModel();
            delegate: Label
            {
                id: label
                text: model.name

                MouseArea
                {
                    anchors.fill: parent
                    onClicked:
                    {
                        // works once until gc()
                        console.log("clicked on " + model.name)

                        // wont work anymore. model is destroyed
                        gc();
                    }
                }
            }

        }
    }
}

c++/mymodel.h:

#include <QAbstractListModel>
#include <QQmlApplicationEngine>
#include <QObject>
#include <QString>
#include <QDebug>

class BoxModel : public QAbstractListModel
{
    Q_OBJECT

public:

    ~BoxModel()
    {
        // see that it does get destroyed
        qDebug() << "~BoxModel()";
    }

    int rowCount(const QModelIndex& parent = QModelIndex()) const override
    {
        return 5;
    }  

    QVariant data(const QModelIndex &index, int role) const override
    {
        int ix = index.row();
        if (ix < 1) return "Larry";
        if (ix < 2) return "Barry";
        if (ix < 3) return "Gary";
        if (ix < 4) return "Harry";
        return "Sally";
    }

    QHash<int, QByteArray> roleNames() const override
    {
        QHash<int, QByteArray> roles;
        roles[Qt::UserRole+1] = "name";
        return roles;
    }

};

class ModelFactory: public QObject
{
    Q_OBJECT

public:

    Q_INVOKABLE BoxModel* makeModel()
    {
        return new BoxModel();
    }    
};

main.cpp 只是注册类型:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <qqmlcontext.h>
#include <qqml.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
#include "mymodel.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    qmlRegisterType<BoxModel>("com.example.qml", 1, 0, "BoxModel");
    qmlRegisterType<ModelFactory>("com.example.qml", 1, 0, "ModelFactory");

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();
}

你看到了什么:

单击任何名称。它会工作一次,之后它们将是未定义的,因为model 变为空。

例如

qml: clicked on Sally
~BoxModel()
qml: clicked on undefined

我的问题是为什么是这个,当我仍然有参考时?

在示例中,onClicked 可以更改为 label.text 而不是 model.name 来修复,但真正的问题是,一般来说,model 是由任何时间的对象,任何数据。例如,当框需要重绘时。数据随机消失,取决于 GC。

我尝试让 c++ 管理动态模型的生命周期。如果我知道什么时候确切地 QML 完成了它,这可能会起作用。

感谢您提供信息和想法。

在 Windows 8.1/qt5.6mingw 上运行

EDIT1:文件作为要点, https://gist.github.com/anonymous/86118b67ec804e6149423c14792f312d

【问题讨论】:

  • 这看起来像一个真正的错误,感谢您提供一个好的、干净的测试用例。去掉工厂,直接在QML中创建实例,是否还有问题:model: new BoxModel()
  • 有趣。如果我将model: maker.makeModel() 更改为model: BoxModel {} 它可以工作。当我有多个这样的盒子时,我将看看这是否能解决我的问题。谢谢。
  • 是的,这种方式可以工作,前提是可以对底层代码模式进行一些更改。它要求BoxModel 能够在没有任何工厂“了解”它的情况下独立存在。我遇到的问题是我需要在内部跟踪这些。必须用单例完成,但它可以这样工作。但是,动态版本也可以正常工作。谢谢。
  • 是什么阻止您在构造函数中将实例添加到全局列表中,并在析构函数中将其删除?你不需要工厂。

标签: qt qml qt5 qt5.6


【解决方案1】:

我知道这是一个老问题,但我刚刚遇到了类似的问题,并且在写我的过程中发现了你的问题。请参阅QObject gets destroyed after being put into QML variable 了解全文,我将在此处引用。

我发现如果我在将 QObject 传递给 QML 之前设置它的父级,那么它不会被删除。因此,我得出的结论是,将无父 QObject 传递到 QML 范围内会使该范围成为 QObject 的父级,并在范围结束后调用其析构函数。

【讨论】:

    【解决方案2】:

    我刚刚在ComboBox 上遇到了同样的问题。

    作为一种解决方法,您可以创建自己的属性以保持对其的强引用:

    Repeater {
        property QtObject myModel: maker.makeModel();
        model: myModel
        // …
    }
    

    【讨论】:

      【解决方案3】:

      正如 Kuba 所说,这确实看起来像一个错误。但是,您可以采用另一种方法并通过QQmlEngine::setObjectOwnership() 自己拥有模型。具体来说,改变

      Q_INVOKABLE BoxModel* makeModel()
      {
          return new BoxModel();
      }
      

      Q_INVOKABLE BoxModel* makeModel()
      {
          BoxModel *model = new BoxModel(this);
          QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership);
          return model;
      }
      

      将解决此问题(请记住将返回的模型作为BoxModel 的父级,以便它被适当地删除)。行为原因解释here

      通常,应用程序不需要明确设置对象的所有权。 QML 使用启发式方法来设置默认所有权。默认情况下,由 QML 创建的对象具有 JavaScriptOwnership。例外情况是通过调用 QQmlComponent::create() 或 QQmlComponent::beginCreate() 创建的根对象,它们默认具有 CppOwnership。这些根级对象的所有权被认为已转移给 C++ 调用者。

      不是由 QML 创建的对象默认具有 CppOwnership。例外情况是从 C++ 方法调用返回的对象;它们的所有权将设置为 JavaScriptOwnership。 这仅适用于显式调用 Q_INVOKABLE 方法或插槽,但不适用于属性 getter 调用。

      【讨论】:

      • 这不是真正的答案。 JavaScriptOwnership 的含义正是提问者所期望的:JS 引擎将管理生命周期,并且只有在不再引用对象时才销毁对象。在询问者的情况下,这是一个明显的错误,因为model 是一个实时引用,但是被引用的对象被破坏了。绝不应该是这样!
      • @Mitch,感谢您的回答。我最初确实尝试过CppOwnership。但不知道何时删除它们。我还没有找到告诉我 QML 已完成的 QML 通知程序,例如当它破坏封闭视图时。
      • @KubaOber 说得好;这可能是一个错误。尽管如此,这可能是最明智的解决方法。我会更新答案。
      • @jkjyuio 如果您将创建的模型设置为BoxModel 的父级,那么它们将被删除。这似乎是一种非常合理的方式。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多