JustAnotherCurious
JustAnotherCurious

Reputation: 2240

QML memory management

I'm trying to test QML to understand how it works with C++. I have ClassA and ClassB - 2 similar C++ classes. Here is a ClassA. All methods are self explanatory with their names, so I won't place implementation here.

class ClassB;
class ClassA : public QObject
{
    Q_OBJECT
public:
    explicit ClassA(QObject *parent = 0);
    ~ClassA();
    Q_PROPERTY(ClassB* classB READ getClassB WRITE setClassB NOTIFY classBChanged)
    ClassB* getClassB() const;
    void setClassB(ClassB *classB);
signals:
    void classBChanged();
private:
    ClassB *m_classB;
};

ClassB is the same, just change all *lassA* to *lassB* and all *lassB* to *lassA*.

Then I register both classed in QML with

qmlRegisterType<ClassA>("testmodule.test",1,0,"ClassA");
qmlRegisterType<ClassB>("testmodule.test",1,0,"ClassB");

And in QML code on mouse click I create both objects like this:

onClicked: {
    var comp = Qt.createComponent("TClassA.qml"); //TClassA.qml is 
                                                  //a component of type 
                                                  //ClassA
    var ca = comp.createObject();
    comp = Qt.createComponent("TClassB.qml");
    var cb = comp.createObject();
    ca.classB = cb;
    cb.classA = ca;
    parent.blockFromGC = ca;
}

And after that I call garbage collector with gc(). I expected that ca is blocked from removal with parent.blockFromGC and cb is blocked from removal with reference from ca. But garbage collector destroyed cb and after that parent.blockFromGC.classB === null.

So I have second MouseArea with this code:

onClicked: {
    console.log(mouse.button)
//    if (mouse.button == Qt.RightButton) {
//        console.log(parent.vasya.classB)
//    }
    gc();
    console.log(parent.blockFromGC.classB) //I use cb here
}

So when I click the MouseArea I get in console:

qml: 1 //Left button
qml: null //value of parent.blockFromGC.classB
classB destroyed:  TQMLClassB(0x34960d0) //I have qDebug() in destructor

So my object cb was destroyed.

So I have this questions:

1) Is there a way how I can register a C++ type as basic type, so I could write var ca = new ClassA() instead of creating a *.qml file, creating a component and finally creating an object?

2) Why did garbage collector destroyed my cb object and what should I do to keep this object from deleting?

Moreover! If I uncomment those commented lines

//    if (mouse.button == Qt.RightButton) {
//        console.log(parent.vasya.classB)
//    }

regardless the button I press, the object is not destroyed anymore.

qml: 1 //left button
qml: TQMLClassB(0x3df8e90) //object is alive
.....
qml: 2 //right button
qml: TQMLClassB(0x3df8e90) //before gc() - alive
qml: TQMLClassB(0x3df8e90) //after gc() - alive

3) Where can I read about QML memory management in detailes? I find this behaviour really strange..

Addition 1: I played with this situation a bit more and the results were unpredictable. I updated from 5.3 to Qt 5.4 and this behavior with object deletion has gone. The problem is that the behavior was so unpredictable, that the fact I can't reproduce this behavior in Qt 5.4 doesn't mean that the problem is fixed. I'll try to look in bug reports and bug fixes. If I found something, I'll post it here. If not, I'll try to reproduce this situation in Qt 5.4 and post a report.

Upvotes: 3

Views: 6328

Answers (1)

jturcotte
jturcotte

Reputation: 1273

  1. Like any QML type, you can define a component statically within another:

    Component {
        id: classAComponent
        ClassA { }
    }
    onClicked {
        var ca = classAComponent.createObject()
    }
    
  2. There is a subtlety here: assigning a QML object to a QML property will increase its JavaScript ref-count. But an instance stored only in the Q_PROPERTY of a C++ object won't be marked by the garbage collector.

    QML has a dual ownership system. First it defines a hierarchy of QObject/QQuickItem used for display and ownership. Attached to this backbone is a garbage collection system where any QML object can own a tree of JavaScript objects through property var.

    So to keep your ClassB object alive, you either have to keep it in a QML property, or provide a parent for it when calling component.createObject() (it's a hard ownership; it will be destroyed regardless of any JS reference to it when the parent is destroyed)

    Example with the QML property:

    Component {
        id: classAComponent
        ClassA {
            property Item refClassB
        }
    }
    onClicked {
        var ca = classAComponent.createObject()
        ca.refClassB = classBComponent.createObject()
    }
    

    Ideally you should avoid dynamically creating object as much as possible and use your C++ objects statically like normal QML components and let the declarative structure maintain the QObject backbone automatically, like this:

    ClassA {
        classB: ClassB { }
    }
    
  3. Sadly not so much, the best I know of, more for QML than C++ is Dynamic QML Object Creation from JavaScript.

Upvotes: 4

Related Questions