[Development] How qAsConst and qExchange lead to qNN

Marc Mutz marc.mutz at qt.io
Mon Nov 14 17:22:44 CET 2022


Hi Ulf,

On 11.11.22 09:35, Ulf Hermann via Development wrote:
> There is an undeniable benefit of _offering_ QSpan, QStringView, and 
> generator APIs in a few relevant cases:
> 
> 1. Users want to pass a "foreign" container to a Qt function that 
> doesn't only store it as QList or QString. It might merely iterate it or 
> store it as something else.

The assumption that there's a problem only for "foreign containers" is 
incorrect: Take the native Qt container QString as an example. See 
assembly in my QAnyStringView blog post: 
https://www.qt.io/blog/qstringview-diaries-qanystringview You have this 
problem as soon as you pass constant data, which is a common enough 
use-case to warrant optimizing for.

Aside: I find it amusing when people complain that span<>s aren't as 
optimal as (ptr, n) on the broken Windows ABI, but then say that all 
APIs should take owning containers instead. Either we care about 
effciency of argument passing, or not. IMHO, spans strike the optimal 
balance between efficiency and convenience.

The next major problem is subsetting. The Qt containers (QString, 
QByteArray, QList) don't have efficient subsetting. Any form of parsing 
thus directly benefits from views, and the end of parsing will then 
naturally be tokens stored in views. If relevant consumer APIs don't 
take views, but owning containers, you're likewise injecting owning 
container constructions in user code, let alone memory allocations. A 
good API should not have such impedance mismatches between its parsers 
and its data consumers. And please, don't shoot the messenger. 
QStringRef was trying to solve the same problem, just badly (being tied 
to QString without being able to produce a QString from a substring w/o 
deep copy).

Yes, there's fromRawData(), but it doesn't remove the ctor and dtor 
calls of the owning container. And apart from QStringLiteral, no-one uses it. So even if the class stores data in 
native containers, the construction of these native containers is often 
better done centrally instead of being duplicated in each caller.

To summarize: the deep copy often happens already, in user code. By 
using spans, the deep copy still happens, but the code to do so isn't 
duplicated.

> All other cases look much fuzzier to me. QSpan or QStringView (or a 
> generator) may be beneficial or detrimental there, depending on exact 
> usage pattern. The cost we're avoiding is mostly the reference count
 > a far cry from a deep copy.

This is not correct. The ref count of owning containers is certainly not 
the main reason to use views. Otherwise non-implicitly-shared containers 
would be a solutions, which they are not.

As detailed above, we're mainly avoiding the code bloat of owning 
container construction and destruction, as well as the deep-copy on 
subsetting. We also avoid accidental detaches

    for (auto [pos, col] : gradient.stops())
      doSomthingWith(pos, col);

and allow the implementation of a class to choose an optimal data 
structure without causing an impedance mismatch with its own API.

I find it amusing when some people say that the use of NOI constrains 
implementations when the opposite is demonstrably true (QRegion).

Both owning and non-owning interfaces have their advantages and 
disadvantages. Huge code bases like llvm show that you can program just 
as well with NOI (ArrayRef/StringRef) as with owning containers.

And I think at least for setters, the benefits of NOI far outweigh the 
drawbacks. The only drawback I have heard is deep copy. From the same 
people that suggest to favour convenience over efficiency. Well, for 
setters, nothing is more convenient than NOI.

Can we agree that NOI for setters is a no-brainer? Then 90% of the 
usefulness of NOI can already be reaped, in a BC and SC manner. There's 
pretty little we can do with return values before Qt 7, except use the 
stuff in private APIs to try it out. But we can and should convert 
setters already.

> On the flip side we're introducing complex 
> life time problems that will lead to hard to find memory management 
> defects.

Spans add no lifetime problems on top of .data() and/or .begin()/.end(). 
In particular, these are statically detectable (cf. 
https://github.com/isocpp/CppCoreGuidelines/blob/master/docs/Lifetime.pdf, 
implemented in VS 2019 and (experimentally) in Clang), so they're not 
"hard to find".

> I suggest we look at this from the perspective of a _user_ of 
> Qt. I'm pretty sure you can all imagine which problem a user would 
> prefer here.
 >
> So, I suggest we add those "view" APIs to the cases where they provide a 
> clear benefit. For methods that return a span/view/generator, we should 
> always offer a safe alternative that returns an owning container. The 
> naming convention should be:
> 
> 1. Use overloads for methods that take views or spans. In new API we can 
> omit the methods that take owning containers. If the overload set grows 
> out of hand, don't add the view/span alternative until we can remove 
> something. By Thiago's argument, that means not to convert existing 
> methods to QStringView for now.
> 
> 2. Use the postfix "View", "Span" or "Generator" for methods that return 
> views, spans or generators rather than owning containers. This way it's 
> harder for users to mess up the life time.

This reminds me of an interview that Bjarne gave a few years back where 
he recapped, and I'm paraphrasing here, that the committee usually 
insists on extra syntax for new features, to the detriment of users that 
are then agonizing over the complicated mess.

All you're doing by having foo() and fooSpan() is putting the burden of 
using the correct form on the user instead of putting it where it 
belongs: in the compiler.

This stuff is solved by tools, not documentation and uglified APIs.

Thanks,
Marc

-- 
Marc Mutz <marc.mutz at qt.io>
Principal Software Engineer

The Qt Company
Erich-Thilo-Str. 10 12489
Berlin, Germany
www.qt.io

Geschäftsführer: Mika Pälsi, Juha Varelius, Jouni Lintunen
Sitz der Gesellschaft: Berlin,
Registergericht: Amtsgericht Charlottenburg,
HRB 144331 B



More information about the Development mailing list