2

Based on Qt documentation, whenever a QObject pointer type is passed from C++ code to QML, via a Q_INVOKABLE method, there is a set of rules that determine who is responsible for the lifetime of that pointer. Should the QObject be parentless, implicitly the QML engine is responsible for taking ownership of the pointer.

In my scenario, I want my frontend UI to represent a list model which is generated/provided by the backend C++ code. My assumption is that the pointer will stay alive as long as there is a reference to it by the QML code. The code below shows the trimmed down test case:

Main.cpp

#include <QAbstractItemModel>
#include <QDebug>
#include <QGuiApplication>
#include <QObject>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QStringListModel>

class MyStringListModel : public QStringListModel
{
    Q_OBJECT

public:

    explicit MyStringListModel(const QStringList &strings, QObject* parent=nullptr) : QStringListModel(strings, parent)
    {
        qDebug() << "Creation";
    }

    virtual ~MyStringListModel() override
    {
        qDebug() << "Destruction";
    }
};

class Backend : public QObject
{
    Q_OBJECT

public:

    Backend(QObject* parent=nullptr) : QObject(parent)
    {

    }

    Q_INVOKABLE QAbstractItemModel* createModel() const
    {
        static const QStringList months = {
            tr("January"),
            tr("February"),
            tr("March"),
            tr("April"),
            tr("May"),
            tr("June"),
            tr("July"),
            tr("August"),
            tr("September"),
            tr("October"),
            tr("November"),
            tr("December"),
        };

        return new MyStringListModel(months);
    }
};

int main(int argc, char* argv[])
{
    QGuiApplication application(argc, argv);

    qmlRegisterType<QAbstractItemModel>();

    Backend backend;

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("backend", &backend);
    engine.load("qrc:///ui/main.qml");

    return application.exec();
}

#include "main.moc"

Main.qml

import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.1

ApplicationWindow {
    id: window

    width: 200
    height: 250
    visible: true

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 10

        ListView {

            Layout.fillWidth: true
            Layout.fillHeight: true

            model: backend.createModel()
            delegate: Text {
                anchors.horizontalCenter: parent.horizontalCenter
                text: model.display
            }
        }

        Button {
            Layout.alignment: Qt.AlignCenter
            text: qsTr("Garbage Collect")
            onClicked: gc()
        }
    }
}

This is a screenshot of the program:

The moment the user clicks on the button, the garbage collector runs and destroys the model ptr (destruction is evident by the "Creation" and "Destruction" output in the stdout).

I'm curious to know why the pointer was destroyed? I've noticed that it didn't set the ListView as its parent, which is fair enough, I thought that the QML engine would have used some form of reference pointer to try keep track of who still holds a reference to it. Is there a document which gives greater insight into the way in which garbage collection / ownership is implemented.

Likewise, is there a better way of structuring this code while still meeting the demands of passing a parentless QObject back to QML.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • `gc()` is a the global function which you can call from within QML to kick off a garbage collection. This is the [link](http://doc.qt.io/qt-5/qtqml-javascript-qmlglobalobject.html) to the documentation to explain it further. – Rodrigo Reichert Sep 12 '18 at 04:26

1 Answers1

0

It seems that the reason for the destruction is because the object is not being referenced in QML, for example if it is assigned to a property the garbage collector will not affect it:

ApplicationWindow {
    id: window
    width: 200
    height: 250
    visible: true
    property var mymodel: backend.createModel()
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 10
        ListView {
            Layout.fillWidth: true
            Layout.fillHeight: true
            model: mymodel
            delegate: Text {
                anchors.horizontalCenter: parent.horizontalCenter
                text: display
            }
        }
        Button {
            Layout.alignment: Qt.AlignCenter
            text: qsTr("Garbage Collect")
            onClicked: gc()
        }
    }
}
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • You are right, with your code the garbage collector doesn't destroy the model pointed by the `ListView`., although it does raises some interesting issues. If I run the code, it calls the `backend.createModel()` twice, not sure why, but at least on of them isn't garbage collected. Secondly, after querying the objects themselves, they don't seem to have any parent, so the QML engine must be using some form of reference tracking. Would be nice for Qt to clarify their documentation. – Rodrigo Reichert Sep 12 '18 at 05:11
  • @RodrigoReichert In my test, only "Creation" is printed once, will not you be calling it twice?, Try removing the build folder. On the other hand it seems that the gc only applies to objects that are not directly referenced, but when closing the window the model is also deleted. Unfortunately the documentation does not give many details, often we have to experiment, for example the behavior you point out in your question I had at another time for that I remember the workaround, – eyllanesc Sep 12 '18 at 05:21
  • @RodrigoReichert [cont.] but as I see the progress the documentation is improving in each release, I hope in the future these behaviors will be better described. If my answer helps you, do not forget to mark it as correct, if you do not know how to do it, review the [tour], that is the best way to thank. – eyllanesc Sep 12 '18 at 05:21
  • Its strange because I copied/pasted the code verbatim, tried it on a Windows/Mac machine, both called the backend method twice via QML. I'm assuming there might be something wrong with the Qt version 5.10.1. Thanks for your help btw. – Rodrigo Reichert Sep 12 '18 at 05:44
  • @RodrigoReichert I tested on Linux Qt 5.11.1 – eyllanesc Sep 12 '18 at 05:55