Reputation: 2240
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
Reputation: 1273
Like any QML type, you can define a component statically within another:
Component {
id: classAComponent
ClassA { }
}
onClicked {
var ca = classAComponent.createObject()
}
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 { }
}
Sadly not so much, the best I know of, more for QML than C++ is Dynamic QML Object Creation from JavaScript.
Upvotes: 4