2
votes

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.

1
gc() is a the global function which you can call from within QML to kick off a garbage collection. This is the link to the documentation to explain it further.Rodrigo Reichert

1 Answers

-1
votes

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()
        }
    }
}