Реклама

QML - Урок 023. Охота за багами при передаче указателя на QObject в QML

РуководствоQMLQt, QML, указатель, JavaScriptOwnership, pointer, bug974

Одними из самых мерзких и мало предсказуемых багов являются те, которые возникают в неопределённый момент времени. К числу таких можно отнести баг, который проявляется при передаче указателя на QObject в QML слой. Проблема заключается в том, что если у QObject отсутствует родитель, то при передаче в слой QML происходит смена владельца объекта, то есть ему устанавливается JavaScriptOwnership. В итоге, когда в QML слое пропадут все ссылки на данный объект, то он будет удалён сборщиком мусора QML. Соответственно, все ссылки в C++ слое окажутся невалидными. А приложение при попытке обращения по этим ссылкам молча схлопнется, ничего не сообщив о причине краха.

Говоря о неопределённости срабатывания бага, необходимо отметить, что эта неопределённость произрастает из момента сборки мусора. В обычном варианте этот момент может наступить тогда, когда программа откушает пару гигабайт памяти или когда памяти будет не хватать и её можно будет получить, произведя сборку мусора. То есть приложение может без проблем работать достаточно долго без проявления бага, и даже возможно пережить несколько версий, прежде чем баг проявится у какого-нибудь пользователя, который решит сообщить об этом разработчикам.

Стоит отметить, что данное правило не будет применяться к объектам объявленным в качестве Q_PROPERTY.

Но для демонстрации проблемы можно воспользоваться QML функцией gc() , которая ускорит сборку мусора.

Охота за багами

А теперь посмотрим, как это реализовано в программном коде.

Структура проекта

В данном проекте нас интересуют следующие файлы:

  • main.cpp - без его редактирования не получится передать испытуемый объект в QML слой;
  • MyClass.h - заголовочный файл класса для производства проблем;
  • MyClass.cpp - файл исходных кодов класса для производства проблем;
  • main.qml - файл QML слоя, в котором будем уничтожать указатель на объект.

MyClass.h

В тестируемом классе будет создаваться объект с помощью метода createObject(), а использоваться с помощью метода useObject(), соответственно. Как только в слое QML будет обнулён указатель и будет произведена сборка мусора, то метод useObject() положит программу.

#ifndef MYCLASS_H
#define MYCLASS_H

#include <QObject>

class MyClass : public QObject
{
    Q_OBJECT

public:
    Q_INVOKABLE QObject* createObject();    // Создание объекта для испытаний
    Q_INVOKABLE void useObject();           // Использование испытуемого объекта

private:
    QObject* m_object;      // Подопытный кролик
};

#endif // MYCLASS_H

MyClass.cpp

В метода createObject() имеется два вариант инициализации объекта m_object. Первый не закомментированый является вариантом с багом, второй будет работать стабильно. А в методе useObject() будем получать имя объекта, естественно, что в данном случае будем получать пустую строку.

#include "MyClass.h"
#include <QObject>
#include <QDebug>
#include <QString>

QObject *MyClass::createObject()
{
    m_object = new QObject;         // Создаём объект без родителя
//    m_object = new QObject(this);   // Создаём объект с родителем
    return m_object;                // Возвращаем объект
}

void MyClass::useObject()
{
    qDebug() << m_object->objectName(); // Отображаем имя объекта
}

main.cpp

Стандартная регистрация объекта для доступа в контексте QML.

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "MyClass.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MyClass mClass;

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    // Регистрируем тестовый класс в QML слое, демонстрирующий проблему
    engine.rootContext()->setContextProperty("myClass", &mClass);

    return app.exec();
}

main.qml

В проект создано три кнопки:

  1. Создаст объект и передаст его указатель свойству objectFromCppWorld;
  2. Обнулит указатель, создав баг;
  3. Попытается использовать объект C++ слоя. И метод будет работать, пока не произойдёт сборка мусора.
import QtQuick 2.5
import QtQuick.Controls 1.3

ApplicationWindow {
    width: 640
    height: 480
    visible: true

    property QtObject objectFromCppWorld: null

    /* Для демонстрации проблемы со сменой родителя объекта
     * сделаем три кнопки, которые будем активировать последовательно.
     * Первая создаст объект в C++ слое и передаст указатель на него в QML слой
     * Вторая обнулит указатель и запустит сборку мусора
     * Третья попробует использовать объект
     */
    Column {
        anchors.centerIn: parent
        Button {
            text: "create object in C++, save the pointer to it in C++ world and pass it to QML"
            onClicked: objectFromCppWorld = myClass.createObject()
        }
        Button {
            text: "null the reference in QML"
            onClicked: {
                objectFromCppWorld = null
                gc()
            }
        }
        Button {
            // Когда произойдет сборка мусора, использовать объект без родителя не получится
            text: "use created object in C++"
            onClicked: myClass.useObject()
        }
    }
}

Соавтор статьи: Владимир Курман

Видеоурок

Реклама

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
Последние комментарии
  • EVILEG
  • 13 июля 2017 г. 2:12

Qt/C++ - Урок 023. Перетаскивание QGraphicsItem на QGraphicsScene мышью

Ну например так можете сделать.void MoveItem::mousePressEvent(QGraphicsSceneMouseEvent *event){ if (QApplication::mouseButtons() == Qt::RightButton) { this->deleteLa...

  • Mark
  • 13 июля 2017 г. 1:26

Qt/C++ - Урок 023. Перетаскивание QGraphicsItem на QGraphicsScene мышью

Подскажите пожалуйста как в данном проекте по перетаскиванию организовать удаление объекта со scene методом delete item, допустим при щелчке ПКМ по объекту QGraphicsScene. Мои попытки оказалис...

  • EVILEG
  • 10 июля 2017 г. 21:34

Qt/C++ - Урок 048. QThread - работа с потоками с помощью moveToThread

А что делали? Повторяете урок или как? Пытались просто скачать проект в конце статьи и запустить?

Qt/C++ - Урок 048. QThread - работа с потоками с помощью moveToThread

У меня происходит переполнение счетчика count, появляется ошибка malloc(): memory corruption (fast). Не подскажите, как с этим бороться?

  • EVILEG
  • 9 июля 2017 г. 2:07

GameDev на Qt - Урок 3. Уничтожение противников

Поэтому в пятом уроке есть исходники всего проекта )))). Вообще, все эти материалы были не предыдущей версии сайта, которая на WordPress. Во время переноса мог что-то потерять.

Сейчас обсуждают на форуме

тестирование классов в QT

это вопрос.

Как реализовать отправку e-mail

Возможно что уже и нет необходимости в почтовом клиенте, но в своё время так же столкнулся с данной проблемой в QT. Нашел один интересный проект под названием libqxt, там реализовано дов...

  • Asteri
  • 14 июля 2017 г. 12:23

css

Делюсь, может, пригодится когда-нибудь) QTableView QHeaderView { background-color: #ffffff; } Вот так эта проблема лечится, градиент задать не получается, но хоть...

  • EVILEG
  • 12 июля 2017 г. 19:52

QSqlQuery выполнение sql запросов из файла

Мне думается, что это уже будет дело вкуса и вашего взгляда на проект. Если Вы действительно собираетесь просто скармливать своему софту SQL скрипты, которые должны будут выполняться для...

QML Canvas + Line. Bug?

Вот оно что, значит не баг) Спасибо