On 2019-05-16 18:29, Thiago Macieira wrote:
On Thursday, 16 May 2019 08:26:08 PDT Mutz, Marc via Development wrote:
[...]
I believe the opposite to be true: I believe owning container use in the
API to break a class' encapsulation:

If you return QVector<>, which choices do you have for the internal data
structure? A QVector.

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?

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.

If you return a view, you can use a std::vector, a QVector, a C array
[...]

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.

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.

And no, you _can_ write views for node-based containers, too. Cf. how
QVariant is (indirectly) iterable these days.

Sure, but you'd have to change the public API to return that view instead of the contiguous-storage one. Unless we predict that that would be the case and
always return a view that uses an indirect data-access method, which
potentially owns the data. Does such a view exist?

Of course it does not. But see above.

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.

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).

QGradient::stops is a good example that synthesises
data on-demand in one case:

    if (m_stops.isEmpty()) {
        QGradientStops tmp;
tmp << QGradientStop(0, Qt::black) << QGradientStop(1, Qt::white);
        return tmp;
    }
    return m_stops;

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.

Thanks,
Marc

[1] Paraphrasing what Alex Stepanov teaches in his A9 courses: No C programmer would _ever_ get the idea to use a self-rebalancing red-black tree for something that holds a dozen elements. Because once you understand what is required to implement one, you'd shy away from the sheer complexity. Yet, in C++, just typing QMap makes the compiler do all that stuff for you. Don't use a map or a hash just because you can and the API is convenient. Use it when it makes sense, given what data is expected to be stored. And you will invariably end up with using vectors all over the place. According to Stepanov, developers wishing to use a map should seek a face-to-face meeting with their manager to explain why they need it :)
_______________________________________________
Development mailing list
Development@qt-project.org
https://lists.qt-project.org/listinfo/development

Reply via email to