[Development] On QML, ownership, QObject-trees and QSharedPointer

christopher.adams at nokia.com christopher.adams at nokia.com
Thu May 24 04:41:31 CEST 2012


Hi Rene,

I certainly agree that constructive discussion about the QML language and ways that it could be improved are important.  You raise a lot of important points, some of which will be addressed in Qt5.0, some of which we still need to consider how to fix, and what priority they should be, for future minor releases of Qt.

More comments inline (some sections removed for brevity).

> OWNERSHIP...
> 
> These are the ownership "universes" (unless I'm mistaken):
> 
> 1) Objects created in C++ owned via the QObject parent/child tree
> e.g.: new MyGizmo.
> 2) Objects created in C++ owned via the QSharedPointer system, e.g.:
> QSharedPointer (new MyGizmo).
> 3) Objects created by QDeclarativeEngine or Javascript owned by the engine,
> e.g.: MyGizmo { ... properties ... }
> 
> Question: How can we expose objects governed by QSharedPointer to QML
> safely? I *can* guarantee the lifecycle beyond the life of my
> QDeclarativeEngine.

Currently, QML isn't aware of QSharedPointer and friends, but internally uses its own guard-types to react to QObject deletion.
If you want to ensure that the engine won't delete the QObject you pass in, you need to explicitly set the ownership of the QObject to CppOwnership (there's a QQmlEngine function to do so).
Alternatively, if the QObject has a parent (ownership parent) set, the engine won't delete it.

> 
> These are the ways to transfer values/references from C++ to QML (let's
> ignore the other way around for now):
> 
> A) By calling QDeclarative::setRootContext (ownership not transferred:
> http://qt-project.org/doc/qt-
> 4.8/qdeclarativecontext.html#setContextProperty).
> B) By calling QObject::setProperty on the instantiated component
> (ownership?)
> C) By QML calling a method on a C++ object and getting a response
> (ownership IS transferred to QML. In the docs somewhere.)
> D) By QML accessing a Q_PROPERTY on a C++ object (ownership?).
> 
> Now, A+B lend themselves badly as mechanisms to expose subobjects, but
> makes it easy to specify ownership via
> QDeclarativeEngine::setObjectOwnership.
> Conversely C+D are better at exposing subobjects or related collections (in
> QML... myProperty: myGizmo.findAllTimelineEvents() ), but I don't see how I
> can specify ownership in a QML-agnostic way (C++ models should not know
> about QML!)

I don't understand your last point.  What do you mean "in a QML-agnostic way"?  The QML engine respects normal QObject parenting.
You are correct about (c) by the way: if the QObject returned from a Q_INVOKABLE function to JS does not have CppOwnership explicitly set, it will become JavaScriptOwnership owned.  To avoid that, you can explicitly set the ownership semantic prior to returning it.
With (b) ownership is not transferred.  However, it should be noted that if you define a QObject-derived-type property in QML with its value declared (ie, via "property Item propName: Item { id: child }") that property's QObject value will be owned by the object with the propName property.  But this isn't directly related to interacting with QML from C++, and so is probably unrelated to what you are talking about.

> 
> Question: What happens to ownership in each of these transfers?
>

To summarise:
a) no ownership change
b) no ownership change
c) ownership change if ownership semantics were not previously explicitly set
d) no ownership change

> TYPESYSTEM...
> 
> Then there is the issue of what type is associated with the object.
> This is relevant when specifying properties in QML. I believe that a major
> source of confusion comes from the notion that there are three type
> systems involved:
> 
> 1) The traditional Qt/C++ moc/compiler types (perhaps QObjects, perhaps
> declared Q_DECLARE_METATYPE to make QVariant understand them)
> 2) The types that the Javascript engine knows (Object, Function, Number,
> String etc.)
> 3) The types that the QML engine understands (http://doc-snapshot.qt-
> project.org/4.8/qdeclarativebasictypes.html
> plus those registered with qmlRegisterType). QVariants can be used too.
> 
> Question: Are there 2 or 3 typesystems?

I guess it depends on how you define the term.
There are only two property types that the QML engine understands: JavaScript vars, and QVariant properties.
It just happens that it builds type information from the loaded component set, and enforces type safety when assigning to properties of those types (which is why you cannot assign an Item {} to a property of type Rectangle).
But internally, all properties are stored as either QVariants or JavaScript vars.

> 
> I assume that each time a value crosses a boundary between from C++ =>
> QML => Javascript an automatic conversion takes place. This conversion is
> probably the root of all evils, since a lot can happen with pointer references
> being smashed to either some form of null (as in unsuccessful C++ to QML
> assignment via (A) or (B) above) or "undefined" (like when a javascript
> expression tries to access a QML Component property) depending on factors
> not quite clear to me.
> 
> Question: What are the exact mechanisms that governs value conversion
> from one space to another?

Correct, the conversion can be a source of problems.
Whenever a value stored in a QVariant property is exposed to a JavaScript expression (binding, signal handler, dynamic method, etc), it is converted to a JavaScript value.
In the case of basic types (numbers, Booleans, strings and so on) this is fairly simple.  In case of component-defined QObject-derived types, we create a JavaScript object which is "special" - we install some functions on that object such that any property lookup or function call on it, is passed to our code in C++ which performs the appropriate lookup / call.  QObject-types are not the only types for which we construct these sorts of special objects; there are quite a few different types for which we do this (eg, certain sequence types such as QList<int> etc, as well as internal implementation detail classes for which we need to intercept symbol resolution, and so on).

Whenever a value in JavaScript is assigned to a QVariant-backed property of a QML item, we do a similar (but inverse) conversion (if it's a JavaScript string, we convert it to a QString, if it's a "special" JavaScript object, we "pull out of it" the embedded pointer to the actual object.

You mention strangeness around certain special JavaScript values like "undefined" and "null".  Yes.  There are some special semantics defined which say: if "undefined" is assigned to a QVariant property which is Reset-able, we reset the property.  Null is generally treated as a null-ptr when assigned to QObject-derived-type properties, but otherwise may fail, depending on the property type.

In QtQuick 2.0 (ie, Qt 5.0), we are thinking about using property var more often in the implementation (eg, of qobject-derived-type properties) to avoid some of those edge-cases, and providing more consistent (and useful) referencing semantics.

> 
> COLLECTIONS...
> 
> Collections (QList, QMap, QHash, QSet) of object references constitutes a
> special scenario which in my view is less that ideally solved. I would love a
> much simpler and more intuitive way to expose those to QML. But given that
> this topic is way too large, I don't want to bring it up here, although it does
> belong.

This comes up regularly.  In Qt 5.0 we added support for several "sequence types" (at the moment, just QList of int, bool, qreal, QString, QUrl, as these were identified as the most commonly used sequence types).  There is no built-in support for hashes, maps, sets, etc currently, except that QVariantMap is converted to a generic JavaScript object with key-value pairs set as correctly as possible (again, according to the conversion semantics for each value's type).

There are of course issues with exposing such things to QML/JS (eg, JS uses a sparse-Array implementation, which we could use for such properties but that entails a huge performance hit; alternatively, we could (as we currently do) use QList as the backingstore for the exposed object, meaning access is fast, but it's not sparse (and cannot include "undefined" values for those indexes which haven't been set yet, and so on).  We aim for conformance, but sometimes the performance trade-off isn't (IMO) worth it.  We do need to clearly document such differences when and where they occur, however.  Documentation, in general, is something that we are looking at improving greatly over the coming few weeks, as we realize that it needs improvement.

> 
> DOCUMENTATION...
> 
> The truth is that initially in ten out of ten cases I resorted to guessing about
> the type and ownership of a value when I cross a boundary. Since I have
> thoroughly cross examined the docs, it must mean that (subjectively) I find it
> hard to extract the proper information from them. I miss clear best practices,
> and often feel more confused by reading the docs than I feel enlightened. In
> fact, let me upgrade that statement:

You don't need to.  I agree.  Rest assured, we're actively working on improving the documentation as we speak.
We will be iterating improvements over the coming weeks.  Hopefully the results will be positive.

> 
> We are quite a lot of programmers who do not have the luxury of reading
> long passages of prose in documentation anymore! (For the same reason I
> have avoided writing this email as long as I could to spare
> *you* the burden of reading messy prose text). We need quick reference
> cards containing COMPLETE information about all possibilities in one place.
> You are only asking for trouble if you make it mandatory to read every article
> under http://doc-snapshot.qt-project.org/4.8/qtquick.html to be able to
> write bug free code.
> Sorry. But I'm learning a new technology just about every week - most in
> vain. I get my dose of documentation, thank you.
> 
> Hoping for an enriching debate,

Thanks :-)

> 
> Best regards,
> Rene Jensen

Cheers,
Chris.



More information about the Development mailing list