[Development] Views
Thiago Macieira
thiago.macieira at intel.com
Fri May 17 17:05:48 CEST 2019
On Thursday, 16 May 2019 11:18:08 PDT Mutz, Marc via Development wrote:
> > When you first design the class, sure. But 5 years later, you may have
> > the
> > data internally kept in a QMap or QHash, mapped to some other
> > information. So
> > your function that used to "return d->member;" now does
> > "return d->member.keys();"
>
> Can you point out a Qt class where this was the case in the past?
I'll have to look it up, but it has happened in the past and if not in Qt,
then in one of the KDE libraries. The Library API Design Policy includes this
particular point (don't return references) for a reason.
> > Another case would be where you're keeping extra data in the internal
> > structure and you need to filter that out before returning. Or the
> > dual:
> > augment with some implied data. The latter could be quite common if the
> > class
> > is not storing anything in the regular case, but synthesising it on
> > demand for
> > the benefit of the old API.
>
> This one is simple: Array of struct -> struct of arrays. Well-known
> optimisation in the games industry.
Sure, but if you returned a view of structs, how would you later make that
view of structs work with your internally-stored struct of arrays?
> > But only so long as each of those containers store lay the data out the
> > same
> > way in memory, which is not the case for my QMap example.
>
> You basically have a dichotomy: Either you have contiguous storage, in
> which case you return pointers, or you have non-contiguous storage, in
> which case you need some form of runtime-dispatch Iterator Pattern.
Or you have a well-known storage pattern, in which case you don't return an
array of pointers (which would need to be allocated anyway), but you return a
means of finding those elements. That's what the containers do for you.
So I don't have a problem returning a type that is cheap when you're designing
the API, so long as you don't lock yourself into it if you later want to
change. I don't see anything but actual containers here.
> I don't think you'd ever come into a situation where you'd need to
> switch from one to the other. If you think so, please provide an example
> where this was necessary in the past.
I'll look it up. Note that in hindsight all of those will look like bad
design, but the point is that we didn't know any better when the design was
first done.
> > Two of your examples [QRegion, QGradient] basically return an internal
> > structure, so I'm not seeing
> > how they are relevant.
>
> How are they not relevant? Because the class basically _is_ a container?
> Well, then see QAIM::roleNames(). Apart from the questionable design to
> return a node-based associative container for the O(10) elements in
> there, instead of a (view to a) sorted array of structs[1], it is a
> prime example of how the API requires a particular implementation. I
> don't remember whether it returns a QMap or a QHash, but either way, one
> might want to return the other, down the road - or even depending on the
> number of elements in the container, like QRegion does.
I meant they are not good examples because they always return something that
currently is stored in the d pointer. So there's no problem in changing them
to return a view to that storage, since that storage does not go out of scope
at function exit.
> For an example of where roleNames() goes horribly wrong, see
> QQmlListModel. Is has the data stored elsewhere and re-constructs a
> QHash each time it's called. With a runtime iterator, it could probably
> produce the values on the fly, and with a sorted array of {role, name}
> it would allocate one block of memory per call, not O(n).
I don't understand what you meant here. Are you advocating that the return
type should have been different? What type would have helped?
Or are you saying that if we had different tools internally, the
implementation of QQmlListModel::roleNames could have been better?
Either way, this is a very good example for why the return type mustn't be a
reference or a non-owning container: the two sources for the function are not
compatible with the return type.
> > How would you implement this one with a view-based return?
>
> Glad you asked:
>
> static constexpr QGradientStop defaultStops[] = {{0,
> QColorLiterals::black}, {1, QColorLiterals::white}};
> return SomeView{std::begin(defaultStops), std::end(defaultStops)};
>
> Instead of the simple solution shown here, what we'll more likely see is
> QVector::fromRawData(). Which is trying to retrofit a view onto an
> owning container. Yuck.
It's a price we pay for flexibility. No doubt that a view is much cheaper to
constructor, pass and destruct. But it's also more limited.
--
Thiago Macieira - thiago.macieira (AT) intel.com
Software Architect - Intel System Software Products
More information about the Development
mailing list