- 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() } } }
Соавтор статті: Володимир Курман