- 1. Охота за багами
- 1. Структура проекта
- 2. MyClass.h
- 3. MyClass.cpp
- 4. main.cpp
- 5. main.qml
- 2. Видеоурок
Одними из самых мерзких и мало предсказуемых багов являются те, которые возникают в неопределённый момент времени. К числу таких можно отнести баг, который проявляется при передаче указателя на 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
В проект создано три кнопки:
- Создаст объект и передаст его указатель свойству objectFromCppWorld;
- Обнулит указатель, создав баг;
- Попытается использовать объект 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() } } }
Соавтор статьи: Владимир Курман