Qt5.6 QML, почему динамические модели уничтожаются после сборки мусора?

У меня есть переменное количество компонентов, поэтому я пытаюсь дать каждому свой собственный model. В этом примере я просто создаю один, но идея та же.

GC() немного случайный, поэтому в примере я форсирую gc() после щелчка, чтобы устранить проблему. Что происходит, так это то, что model уничтожается и становится нулевым. после этого метод click не может его использовать.

основной.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();
                    }
                }
            }

        }
    }
}

С++/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 становится нулевым.

eg

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

Мой вопрос: почему это, когда у меня все еще есть ссылка на это?

В примере onClicked можно изменить на label.text, а не model.name для исправления, но настоящая проблема заключается в том, что, как правило, model доступен объекту в любое время для любых данных. Например, когда коробку нужно перерисовать. случайно данные исчезли, в зависимости от GC.

Я пытался заставить С++ управлять жизнью динамической модели. это могло бы сработать, если бы я знал, когда точно QML покончил с этим.

спасибо за информацию и идеи.

работает на windows 8.1/qt5.6mingw

EDIT1: файлы как суть, https://gist.github.com/anonymous/86118b67ec804e6149423c14792f312d


person jkj yuio    schedule 19.05.2016    source источник
comment
Это похоже на настоящую ошибку, спасибо за хороший, чистый тестовый пример. У вас все еще есть проблема, если вы удалите фабрику и создадите экземпляр непосредственно в QML: model: new BoxModel()?   -  person Kuba hasn't forgotten Monica    schedule 19.05.2016
comment
интересный. если я изменю model: maker.makeModel() на model: BoxModel {}, это сработает. я собираюсь посмотреть, решит ли это мою проблему, когда у меня будет несколько таких ящиков. Благодарю.   -  person jkj yuio    schedule 19.05.2016
comment
Да, этот способ может работать, при условии, что некоторые изменения могут быть внесены в базовый режим кода. для этого требуется, чтобы BoxModel мог существовать сам по себе, чтобы ни одна фабрика не знала об этом. У меня была проблема в том, что мне нужно было отслеживать их внутри. должно было быть сделано с синглтоном, но это может работать таким образом. однако было бы хорошо, чтобы динамическая версия тоже работала. Благодарю.   -  person jkj yuio    schedule 19.05.2016
comment
Что мешает вам добавить экземпляр в глобальный список в конструкторе и удалить его в деструкторе? Для этого не нужен завод.   -  person Kuba hasn't forgotten Monica    schedule 20.05.2016


Ответы (3)


Как сказал Куба, это действительно похоже на ошибку. Однако вы можете выбрать другой подход и стать владельцем моделей самостоятельно через QQmlEngine::setObjectOwnership(). В частности, изменение

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

to

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

исправит это (не забудьте родить возвращенную модель BoxModel, чтобы она была удалена соответствующим образом). Причина такого поведения объясняется здесь:

Как правило, приложению не требуется явно устанавливать принадлежность объекта. QML использует эвристику для установки владельца по умолчанию. По умолчанию объект, созданный QML, имеет JavaScriptOwnership. Исключением являются корневые объекты, созданные вызовом QQmlComponent::create() или QQmlComponent::beginCreate(), которые по умолчанию имеют CppOwnership. Считается, что право собственности на эти объекты корневого уровня было передано вызывающему объекту C++.

Объекты, не созданные с помощью QML, по умолчанию имеют CppOwnership. Исключением являются объекты, возвращаемые вызовами методов C++; для них будет установлено значение JavaScriptOwnership. Это применимо только к явным вызовам методов или слотов Q_INVOKABLE, но не к вызовам методов получения свойств.

person Mitch    schedule 19.05.2016
comment
Это не совсем ответ. Значение JavaScriptOwnership именно то, что ожидал автор вопроса: механизм JS будет управлять временем жизни и уничтожать объект только тогда, когда на него больше нет ссылок. В случае спрашивающего это явная ошибка, поскольку model является активной ссылкой, однако объект, на который указывает ссылка, уничтожается. Такого никогда не должно быть! - person Kuba hasn't forgotten Monica; 19.05.2016
comment
@ Митч, спасибо за ответ. Я пробовал CppOwnership изначально. но не знаю, когда их удалить. я не нашел уведомителя QML, который сообщает мне, что QML выполнен, например, когда он уничтожает закрывающее представление. - person jkj yuio; 19.05.2016
comment
@KubaOber, это хороший момент; наверное это баг. Тем не менее, это, пожалуй, самый разумный обходной путь. Я обновлю ответ. - person Mitch; 19.05.2016
comment
@jkjyuio, если вы создаете модели, которые вы создаете, BoxModel, они будут удалены, когда это произойдет. Это кажется довольно разумным способом сделать это. - person Mitch; 19.05.2016

У меня была такая же проблема с ComboBox.

В качестве обходного пути вы можете создать собственное свойство, чтобы сохранить сильную ссылку на него:

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

Я знаю, что это старый вопрос, но я только что столкнулся с подобной проблемой и нашел ваш вопрос в процессе написания моего. См. QObject уничтожается после помещения в переменную QML для полной истории, и я приведу ее здесь.

Я понял, что если я установлю родительский объект этого QObject до того, как передам его в QML, он не будет удален. Итак, я пришел к выводу, что передача QObject без родителей в область QML делает эту область родителем QObject и вызывает его деструктор после окончания области.

person ProdoElmit    schedule 15.02.2017