[Development] Models for QML (was Re: The future of smart pointers in Qt API)

Ola Røer Thorsen ola at silentwings.no
Tue Feb 4 21:33:39 CET 2020


Hi Shawn!

tir. 4. feb. 2020 kl. 08:15 skrev Shawn Rutledge <Shawn.Rutledge at qt.io>:

>
> > On 3 Feb 2020, at 10:26, Ola Røer Thorsen <ola at silentwings.no> wrote:
> >
> > - The biggest struggle I have with Qt currently regarding pointers is
> when you share a dynamic model of QObject* to qml. Here it would actually
> be useful to have shared_ptr, and I've solved this with some Q_GADGET-based
> wrappers. (One could argue I should not share QObject* stuff in a model,
> but putting everything in model roles does not scale well when the objects
> have properties that change often, or have properties to other objects.
> Hard to maintain, redundant code, no error handling in qml if a role is
> mistyped, and QVariant's performance is not good.)
>
> Yes QVariant gets in the way.  But AFAIK there is no plan to change it,
> because QAIM is too entrenched.  (How else can you write one model that
> will work for views in both widgets and Quick?)
>

Yeah that makes sense. It also works just fine as long as the models don't
change the values of the roles that often.

What do you see as the argument against putting QObjects in models then?  I
> tend to think if you are creating a model for a table or a list, you can
> probably store your data more compactly as an implementation detail of a
> model rather than as a vector of QObject*; but OTOH if you already have
> QObjects for some other reason, it’s OK to expose them, isn’t it?  Or if
> your data set is bounded, not too big, maybe there could even be reasons to
> prefer a vector of objects?  (Apropos
> https://codereview.qt-project.org/c/qt/qtwebengine/+/286972 : I’m not
> sure that should really be a QALM.  I started thinking that it is not how I
> will choose to expose a list of links to a widget-based view, so maybe I
> can reconsider how to expose it to Qt Quick too.  But the usual argument is
> that it’s wasteful to create QObjects just for the sake of it: not items,
> not widgets; and in this case they have no other reason to be QObjects.
> But what else could they be?  Gadgets?)
>

In my case I have the QObjects in a backend for the UI. They have lots of
properties, some which are pointers to other objects, and others which have
values that change at frequencies up to 30 Hz or so. There are also methods
to call on a particular QObject instance in the model. The backend uses
lots of threads to "do stuff" so QObjects, QThread, queued connection
signals/slots are very powerful to get things done.

Example use case - embedded device showing many live video feeds in a grid,
with HUD-style overlays with dynamic metadata, and each feed gives controls
on screen for manipulating camera settings. Feeds come and go "at random".

The issues come when I start removing rows from the model, and delete the
QObjects from those rows. Then you get all kinds of error-messages from QML
in the log which I cannot live with (getting used to them will make me miss
more important errors). Earlier I've also had seemingly random crashes in
the qml engine, but not recently. It might have been in the earlier Qt5
versions, or because I forgot to use deleteLater instead of just deleting
the objects right away. I got rid of these issues using shared_ptr in
wrappers, I'll explain below.

So I'm happy to use QObject* in a model, it just does not feel like it's
completely supported or intended to be used this way as soon as there are
rows to remove runtime.

I’m also curious about your solution with Q_GADGET, since gadgets have some
> shortcomings in QML (some of which I hope we will improve in Qt 6).  If we
> continue with the existing restriction that a gadget is always passed
> between QML and C++ by value, never by pointer, then any gadget that stores
> “too much” data to copy every time should have a d-pointer, right?
>

My Q_GADGET struct has a single member which is a std::shared_ptr to the
real object, and it is registered as a qMetaType and
qmlRegisterUncreatableType to really make it famous. The struct is
accessible in the model as a role. Then I have a QObject-based helper class
that has two properties - one which is this gadget, another with is a raw
pointer to the object referenced by the gadget (smart pointer). This class
is registered with qmlRegisterType. In a view delegate, I instantiate this
helper-class, set the gadget property from the role name, and from there, I
access the raw pointer to the real object I'm after from the raw pointer
property in the helper. Now I have an object owned by QML, that will live
as long as the delegate lives in the view, that holds a shared ptr to my
backend object. And I have access to all the properties and methods. (hope
that made sense! :P )

I've tested it a lot to make sure that it's actually the qml/js
garbagecollector that triggers the deletion of the object once it's taken
out of the models and nobody cares about them anymore. There is a certain
lag when you take something out of a model, until the views all catch up -
especially if there are animations/transitions involved. I use a custom
deleter in the shared_ptr that calls deleteLater on the object from the
main thread btw.

It would be nice to have this done more easily somehow. Apart from that I'm
really looking forward to a Qt Quick version with compile-time error
messages, C++ types (no worries int64 will get rounded to double...) and no
javascript runtime.

Cheers,
Ola
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.qt-project.org/pipermail/development/attachments/20200204/e4b0ff34/attachment.html>


More information about the Development mailing list