[Development] How qAsConst and qExchange lead to qNN

A. Pönitz apoenitz at t-online.de
Wed Nov 16 20:52:38 CET 2022

On Wed, Nov 16, 2022 at 09:50:35AM -0800, Thiago Macieira wrote:
> On Tuesday, 15 November 2022 23:50:38 PST Marc Mutz via Development wrote:
> > > in a thread-safe manner (such that if something in
> > > the same thread or another thread-safely modifies that map, the original
> > > user isn't affected).
> > 
> > The above isn't thread-safe, it isn't even re-entrant, in the same way
> > that iteration using iterators isn't. This is a known issue whenever you
> > hand out references, and it's nothing that violates our
> > const-is-thread-safe promise, 
> No, but it moves the responsibility for avoiding this problem to the user.
> Right now, you can do:
>   for (auto elem : object.keyList()) {
>       operate(); // may recurse back into object and modify it
>   }
> If you use a generator paradigm to return this key list, then the *user* must 
> know that they must create a local container with the items to be generated 
> and iterate over that. Performance-wise, this no different than if the Qt code 
> created the container and returned it, but it has two drawbacks:
> 1) the responsibility for knowing this
> 2) if the Qt object already has a QList with this, then using a generator 
> paradigm enforces the need of a deep copy, when implicit would have been 
> cheaper


The only *safe* *and* *conveniently uniform* (a.k.a. "worth to have on a
Qt API level") use of a generator as return value (e.g. something that's
easily put into a delayed call) is to effectively store a full copy of
the later generated elements /somewhere/ or otherwise make sure that the
base object's lifetime or at least a subset of it that is capable to
create the elements is extended until the generator object dies (e.g.
by having some kind of ref count and COW on itself).

Currently this "storing of a full copy" is bumping a reference count
on an already existing Qt container, or creating one from scratch,
including a deep copy. In the envisioned new world, this would always
be an immediate full copy.

> > > Because you pointed to QStringTokenizer and that implicitly-
> > > copies a QString.
> > 
> > That's imprecise. QStringTokenizer extends rvalue lifetimes ("rvalue
> > pinning") so's to make this safe:
> > 
> >     for (auto part : qTokenize(label->text(), u';'))
> BTW, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2012r2.pdf is 
> accepted for C++23 and moves the end of the temporaries' lifetimes to the end 
> of the full for statement.
> Though we still need to work with C++17 and 20 for a while.
> Also, sometimes I wonder if all the work you and I do to optimise these things 
> matter, in the end. We may save 0.5% of the CPU time, only for that to be 
> dwarfed by whatever QtGui, QtQml are doing.

The Qt Project has easy access to a not completely trivially-sized test
bed for API experiments which has stability promises one magnitude less
and potential damage done to downstream dependencies several magnitudes
less than similar activities in QtBase: Qt Creator. Interested parties
could just check out the sources, and start running experiments without
being bound to long-term API contract and with simple reverts at hand
when experiments fail.

I can even give a hint where to start: A rather heavily used string-ish
class is Utils::FilePath, and there are at least half a dozen "obvious"
candidates for an NOI: E.g. .suffix() currently returns a QString that
is /always/ a copy of a part of the underlying storage, so according to
my understanding this "obviously" would "have to be" a QStringView in a
"new world" API.

I'd be happy to see the results of the experiment from someone who
thinks this kind of API is a good idea in the form of profiler results
and consequences for uses of this API in "user" code. [I know mine.]


More information about the Development mailing list