Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Memory management for list of QObject* results in "Cannot read property X of null" errors
Forum Updated to NodeBB v4.3 + New Features

Memory management for list of QObject* results in "Cannot read property X of null" errors

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
1 Posts 1 Posters 226 Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • A Offline
    A Offline
    ADGB
    wrote on 2 Mar 2022, 10:01 last edited by
    #1

    I need to create dynamic list of QObject* (representin a custom model) and expose them to QML. The problem is that QML tries to re-use previously deleted QObject* which ends up with errors at runtime:

    qrc:/MyWidget.qml:6: TypeError: Cannot read property 'value' of null
    

    Here is my model:

    #include <QObject>
    
    class SubModel : public QObject
    {
        Q_OBJECT
    public:
        SubModel(int value) : m_value(value) {}
    
        Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
    
        int value() { return m_value; }
    
        void setValue(int value)
        {
            m_value = value;
            emit valueChanged();
        }
    
    signals:
        void valueChanged();
    
    private:
        int m_value;
    };
    

    Here is the QAbstractListModel containing the SubModel list (note the createSubModels() method):

    #include <QAbstractListModel>
    #include <QObject>
    #include <QVariant>
    #include <memory>
    #include <vector>
    
    class ModelList : public QAbstractListModel
    {
        Q_OBJECT
    
    public:
        enum ModelRole
        {
            SubModelRole = Qt::UserRole
        };
        Q_ENUM(ModelRole)
    
        void setSubModels(std::vector<std::unique_ptr<SubModel>> subModels)
        {
            beginResetModel();
            m_subModels = std::move(subModels);
            endResetModel();
        }
    
        int rowCount(const QModelIndex& parent = QModelIndex()) const override
        {
            return m_subModels.size();
        }
    
        QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
        {
            if (!index.isValid()) {
                return {};
            }
    
            switch (role) {
            case ModelRole::SubModelRole:
                return QVariant::fromValue<SubModel*>(m_subModels[index.row()].get());
            }
    
            return {};
        }
    
        bool setData(const QModelIndex& index, const QVariant& value, int role) override
        {
            Q_UNUSED(index);
            Q_UNUSED(value);
            Q_UNUSED(role);
            return false;
        }
    
        QHash<int, QByteArray> roleNames() const override
        {
            QHash<int, QByteArray> roles;
            roles[ModelRole::SubModelRole] = "submodel";
            return roles;
        }
    
        Q_INVOKABLE void createSubModels()
        {
            std::vector<std::unique_ptr<SubModel>> subModels;
            for (int i = 0; i < rand() % 5 + 1; i++) {
                subModels.push_back(std::make_unique<SubModel>(rand() % 100));
            }
            setSubModels(std::move(subModels));
        }
    
    private:
        std::vector<std::unique_ptr<SubModel>> m_subModels;
    };
    

    Here is my QML widget using the SubModel instance (note the typed property):

    import QtQuick 2.12
    import MyLib.SubModel 1.0
    
    Text {
        property SubModel subModel;
        text: subModel.value
    }
    

    Here is the main.qml file:

    import QtQuick 2.5
    import QtQuick.Window 2.2
    import QtQuick.Controls 2.15
    
    Window { 
        visible: true
        
        Column {
            Button {
                text: "Create list"
                onClicked: modelList.createSubModels();
            }
    
            Column {
                Repeater {
                    model: modelList
    
                    MyWidget {
                        subModel: model.modelData
                    }
                }
            }
        }
    }
    

    And finally, here is the main.cpp file:

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include <iostream>
    
    #include "models.h"
    
    int main(int argc, char* argv[])
    {
        qmlRegisterUncreatableType<SubModel>(
            "MyLib.SubModel", 1, 0, "SubModel", "This type can't be created in QML");
    
        auto modelList = std::make_unique<ModelList>();
    
        QGuiApplication app(argc, argv);
    
        QQmlApplicationEngine engine;
    
        QQmlContext* rootContext = engine.rootContext();
        rootContext->setContextProperty("modelList", modelList.get());
    
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        return app.exec();
    }
    

    The firs time the "Create list" button is clicked it works fine. But the second time, the initial SubModule pointers are deleted and QML tries to access value attribute of nullptr, resulting in warning. Then m_subModels is replaced and the GUI is effectively updated. I don't understand why QML tries to access model while I emitted the beginResetModel() signal.

    I know that I could change ModelList and make data() returns the actual int value instead of a SubModel pointer but this is not acceptable because I actually have several submodels in real code (separation of concerns).

    Solutions I thought of:

    • Checking for if submodel !== null in QML but this doesn't look good
    • Replacing std::unique_ptr with new but this creates a memory leak
    • Using raw pointer with a Qt parent but memory will never be freed until exit
    • Using QSharedPointer with deleteLater() in destructor but this does not work all the time
    • Using QSharedPointer with setObjectOwnership(JavaScriptOwnership) in destructor but this seems hacky (not tested)
    • Using a QList<QObject*> instead of QAbstractListModel but this generated the same errors
    • Using a QQmlListProperty instead of QAbstractListModel but this generated the same errors
    • Re-using existing SubModel* instead of re-creating the list but this seems very convoluted

    I have spent several days on this problem and I can't find a suitable solution. Would you have an idea please to keep my "SubModel" as it is and avoid QML warnings when the list is re-created?

    1 Reply Last reply
    0

    1/1

    2 Mar 2022, 10:01

    • Login

    • Login or register to search.
    1 out of 1
    • First post
      1/1
      Last post
    0
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Get Qt Extensions
    • Unsolved