Trusted Software Excellence across Desktop and Embedded
Take a glance at the areas of expertise where KDAB excels ranging from swift troubleshooting, ongoing consulting and training to multi-year, large-scale software development projects.
Find out why customers from innovative industries rely on our extensive expertise, including Medical, Biotech, Science, Renewable Energy, Transportation, Mobility, Aviation, Automation, Electronics, Agriculture and Defense.
High-quality Embedded Engineering across the Stack
To successfully develop an embedded device that meets your expectations regarding quality, budget and time to market, all parts of the project need to fit perfectly together.
Learn more about KDAB's expertise in embedded software development.
Where the capabilities of modern mobile devices or web browsers fall short, KDAB engineers help you expertly architect and build high-functioning desktop and workstation applications.
Extensible, Safety-compliant Software for the Medical Sector
Create intelligent, patient-focused medical software and devices and stay ahead with technology that adapts to your needs.
KDAB offers you expertise in developing a broad spectrum of clinical and home-healthcare devices, including but not limited to, internal imaging systems, robotic surgery devices, ventilators and non-invasive monitoring systems.
Building digital dashboards and cockpits with fluid animations and gesture-controlled touchscreens is a big challenge.
In over two decades of developing intricate UI solutions for cars, trucks, tractors, scooters, ships, airplanes and more, the KDAB team has gained market leading expertise in this realm.
Build on Advanced Expertise when creating Modern UIs
KDAB assists you in the creation of user-friendly interfaces designed specifically for industrial process control, manufacturing, and fabrication.
Our specialties encompass the custom design and development of HMIs, enabling product accessibility from embedded systems, remote desktops, and mobile devices on the move.
Legacy software is a growing but often ignored problem across all industries. KDAB helps you elevate your aging code base to meet the dynamic needs of the future.
Whether you want to migrate from an old to a modern GUI toolkit, update to a more recent version, or modernize your code base, you can rely on over 25 years of modernization experience.
KDAB offers a wide range of services to address your software needs including consulting, development, workshops and training tailored to your requirements.
Our expertise spans cross-platform desktop, embedded and 3D application development, using the proven technologies for the job.
When working with KDAB, the first-ever Qt consultancy, you benefit from a deep understanding of Qt internals, that allows us to provide effective solutions, irrespective of the depth or scale of your Qt project.
Qt Services include developing applications, building runtimes, mixing native and web technologies, solving performance issues, and porting problems.
KDAB helps create commercial, scientific or industrial desktop applications from scratch, or update its code or framework to benefit from modern features.
Discover clean, efficient solutions that precisely meet your requirements.
Boost your team's programming skills with in-depth, constantly updated, hands-on training courses delivered by active software engineers who love to teach and share their knowledge.
Our courses cover Modern C++, Qt/QML, Rust, 3D programming, Debugging, Profiling and more.
The collective expertise of KDAB's engineering team is at your disposal to help you choose the software stack for your project or master domain-specific challenges.
Our particular focus is on software technologies you use for cross-platform applications or for embedded devices.
Since 1999, KDAB has been the largest independent Qt consultancy worldwide and today is a Qt Platinum partner. Our experts can help you with any aspect of software development with Qt and QML.
KDAB specializes in Modern C++ development, with a focus on desktop applications, GUI, embedded software, and operating systems.
Our experts are industry-recognized contributors and trainers, leveraging C++'s power and relevance across these domains to deliver high-quality software solutions.
KDAB can guide you incorporating Rust into your project, from as overlapping element to your existing C++ codebase to a complete replacement of your legacy code.
Unique Expertise for Desktop and Embedded Platforms
Whether you are using Linux, Windows, MacOS, Android, iOS or real-time OS, KDAB helps you create performance optimized applications on your preferred platform.
If you are planning to create projects with Slint, a lightweight alternative to standard GUI frameworks especially on low-end hardware, you can rely on the expertise of KDAB being one of the earliest adopters and official service partner of Slint.
KDAB has deep expertise in embedded systems, which coupled with Flutter proficiency, allows us to provide comprehensive support throughout the software development lifecycle.
Our engineers are constantly contributing to the Flutter ecosystem, for example by developing flutter-pi, one of the most used embedders.
KDAB invests significant time in exploring new software technologies to maintain its position as software authority. Benefit from this research and incorporate it eventually into your own project.
Start here to browse infos on the KDAB website(s) and take advantage of useful developer resources like blogs, publications and videos about Qt, C++, Rust, 3D technologies like OpenGL and Vulkan, the KDAB developer tools and more.
The KDAB Youtube channel has become a go-to source for developers looking for high-quality tutorial and information material around software development with Qt/QML, C++, Rust and other technologies.
Click to navigate the all KDAB videos directly on this website.
In over 25 years KDAB has served hundreds of customers from various industries, many of them having become long-term customers who value our unique expertise and dedication.
Learn more about KDAB as a company, understand why we are considered a trusted partner by many and explore project examples in which we have proven to be the right supplier.
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications.
Read more about the history, the values, the team and the founder of the company.
When working with KDAB you can expect quality software and the desired business outcomes thanks to decades of experience gathered in hundreds of projects of different sizes in various industries.
Have a look at selected examples where KDAB has helped customers to succeed with their projects.
KDAB is committed to developing high-quality and high-performance software, and helping other developers deliver to the same high standards.
We create software with pride to improve your engineering and your business, making your products more resilient and maintainable with better performance.
KDAB has been the first certified Qt consulting and software development company in the world, and continues to deliver quality processes that meet or exceed the highest expectations.
In KDAB we value practical software development experience and skills higher than academic degrees. We strive to ensure equal treatment of all our employees regardless of age, ethnicity, gender, sexual orientation, nationality.
Interested? Read more about working at KDAB and how to apply for a job in software engineering or business administration.
(Apologies for the clickbait in the post title! But I'd really like people who are searching for solutions to read this.)
Between Qt 5.14 and Qt 5.15, my colleague, Marc Mutz, and I submitted a series of patches to Qt that added "range constructors" to the Qt containers. This brought Qt containers one step closer to...C++98 feature parity!😀
What's a range constructor? It's a constructor that takes an iterator pair (begin, end) and constructs a container by reading the corresponding range of elements:
// Creates a QList from the [begin, end) rangeQList<int>intList( begin, end );
Usually the range is obtained from another container of a different type. This allows for converting a container to another one, for instance, converting a QList to a QSet:
QList<int> intList =~~~;// Creates a QSet from a QListQSet<int>intSet( intList.begin(), intList.end());
This approach works no matter what is the source container: it can be a QList, a QVector, a std::deque, and what not.
Some Qt containers also provided a similar functionality. For instance, in Qt 5, QList had the toSet() conversion function that returns a QSet with the same elements:
QList<int> intList =~~~;// Creates a QSet from a QList (in Qt 5)QSet<int> intSet = intList.toSet();
The new constructors are a generalization of these conversion functions. They work with any source container, including the ones from the Standard Library, but also those from Qt that were simply lacking the conversion (did you ever notice that there isn't a QVector::toSet()?).
Therefore, in a related set of changes, the conversion functions have been deprecated in Qt 5.15 and then removed in Qt 6.0.
This has made a lot of people very angry and been widely regarded as a bad move.
Wait, that's another thing...
Anyway, on the web, you may find quite a bit of unhappiness about the change. There's a Qt bug report that tries to collect this information, as well as some threads on the Qt mailing lists.
The common factor to the complaints was that the deprecation and subsequent removal looked entirely gratuitous. In other words, is it only because we don't want "duplication" in the APIs that we are asking users to replace perfectly working code with code that is:
functionally identical (does the same thing),
behaviorally identical (it does the same thing in the same way),
and much more verbose and awkward to use (list.toSet() vs QSet(list.begin(), list.end()))?
That sounds silly at best; and certainly against Qt policies of offering nice-to-use APIs.
That's why we...actually have nice things!
Qt has always had convenience methods on its containers. This is perfectly natural in Qt:
QList<int> intList =getAnswers();if(intList.contains(42)) std::cout <<"The Answer has been found!\n";
Only C++23 will perhaps add something as convenient to the Standard Library:
std::vector<int> intVector =getAnswers();if(std::ranges::contains(intVector,42))// if P2302 gets adopted std::cout <<"The Answer has been found!\n";
In the meanwhile, this is the "common" way we do it today:
std::vector<int> intVector =~~~;if(std::find(intVector.begin(), intVector.end(),42)!= intVector.end()) std::cout <<"The Answer has been found!\n";
Quite a mouthful when compared to Qt's way, right?
So if we have standard algorithms (like std::find), why do Qt containers still offer contains, indexOf, and so on? Should we get rid of them, too?
The answer, generally speaking, is no. Qt does not strive for minimality, it strives for convenience. And having those functions is convenient for end users.
The struggle between "easy to use," and "hard to misuse"
Convenience must never be a trade-off for correctness. This is a given.
But convenience should also not let people misuse the APIs for the intended usage. Unfortunately, this is much harder to enforce when simply providing a correct API.
Case in point: toSet() has been introduced as a conversion function from QList to QSet. It does what it says, in the best way possible.
But how and where is toSet() actually used? Do people really need to convert lists to sets all the time?
Turns out that no, they don't! Most of the time, the conversion has been an API abuse. For instance, the conversion has been used to remove duplicates from a QList:
QList<int> list =~~~;// Remove duplicates, and get back to the original list// For the love of kittens, DON'T DO THIS!list = list.toSet().toList();
Or similarly, it has been used to process elements uniquely:
QList<int> list =~~~;// Process elements, skipping duplicates.// This is OK-ish, but there are better solutions!const QSet<int> set = list.toSet();for(int i : set)process(i);
Or to generate an list of unique elements when joining multiple lists:
QSet<int> temp;for(int i =0; i < N;++i){ temp +=getList(i).toSet();}QList<int> result = temp.toList();
All of these usages have a common denominator: they abuse the container API instead of using an algorithm. There's a better, algorithmic solution for most of these. To put in another way, very few of these really require the "convenience" provided by toSet(), and the few ones that do need it are easy to port away to the range constructors.
So why do people immediately jump to using the converting functions between containers?
As a C++ teacher, it's a hard truth for me to digest. Here, I mostly see a failure at educating C++ developers.
Developers using toSet() have no faults. We teach them about containers. We tell them that QList is sequential, random access; that QSet has a random order and doesn't allow for duplicates; and so on. We don't teach them about algorithms, or at least not together with containers; we don't teach about their importance, or we just don't teach about them at all.
So, when faced with a very concrete problem, such as how do I remove duplicates from a QList?, developers immediately jump to QSet. Hey! It's the thing that doesn't allow duplicates in the first place! "So how do I go from a QList to a QSet? Oh, there's converting function, toSet(). Job done; move on."
The correct mental approach of getting rid of duplicates should be different. That is, the default go-to for an algorithmic problem should be an algorithm. Just searching on Google would reveal the correct algorithm to use.
Please note, the fundamental reason why I'd love to spread more knowledge about algorithms isn't just about performance. Yes, removing duplicates via QSet will cost you an awful lot more when compared with a std::sort+std::unique approach:
CPU: hashing, allocating, copying elements around, destroying everything/deallocating
Memory: at least #(number of unique elements) memory allocations (remember, in Qt 5, QSet is implemented as a bucket of chains)
But I don't want to go there. I'm sure that someone will come up with some specific numbers and datatypes for which the QSet approach will actually be faster. In the bigger scale, it's the wrong approach, and we shouldn't let users choose the wrong approach. They deserve better. We're not doing them a favor by supporting APIs such as toSet() and not teaching them the alternatives.
That's the real reason for deprecating toSet(). It's an API which is too easy to misuse, and correctly serves just very few users.
Summary (or: TL;DR: give me the solutions!)
Here are a few quick recipes for going forward.
"I need to eliminate duplicates from a linear, sequential container (QList, QVector, std::vector, etc.)."
If you can reorder the elements in the container itself, then use an algorithm (technically, two: sort and unique; plus a call to the container's erase), it is the fastest option available, at zero extra memory cost.
If you cannot reorder the elements in the container, then use a different algorithm!
KDToolBox::DuplicateTracker<T> tracker;constauto isDuplicate =[&tracker](const T &t){return tracker.hasSeen(t);};container.erase(std::remove_if(container.begin(), container.end(), isDuplicate), container.end());
KDToolBox::DuplicateTracker is a small utility class from KDToolBox, KDAB's collection of miscellaneous useful C++ classes and stuff. It wraps a set and also uses monotonic_buffer_resource to improve the memory usage for small containers, avoiding allocations in those scenarios.
In either case, you may want to also call container.shrink_to_fit(); at the end, if you want to shed extra capacity.
"I need to process unique elements, skipping duplicates."
Use KDToolBox::DuplicateTracker again:
KDToolBox::DuplicateTracker<T> tracker;for(const T &t : container){if(!tracker.hasSeen(t))process(t);}
Of course, bear in mind that this may cost you the same size of container (if it just contains unique elements).
"I really, really need to convert a QList (or another container) to a QSet."
You can use the ranged constructor:
QSet<int>set(list.begin(), list.end());
If you obtain the list from a function call, however, be sure NOT to call the function twice!
QList<int>getList();const QList<int> list =getList();QSet<int>set(list.begin(), list.end());// OK
The double call is expensive and, also, wrong (you're getting potentially two distinct containers back).
"It's still me. I really, really need to convert a QList (or another container) to a QSet. AND I HATE THIS SYNTAX!"
If you can, add range-v3 to your project, and you will be able to do even better:
QSet<int> set1 = list | ranges::to<QSet>();QSet<int> set2 =getList()| ranges::to<QSet>();
This is pretty sweet to type.
"It's still me. I really, really need to convert a QList (or another container) to a QSet. range-v3 sounds sweet, but I can't use it yet -- slows down my compilation times too much, etc."
QSet<int> set1 = list |kdToContainer<QSet>();QSet<int> set2 =getList()|kdToContainer<QSet>();
FAQs
Could Qt have left these methods in Qt 6, although deprecated?
That is a very good question. I personally don't have a good answer. The point of deprecated functionality is that, sooner or later, it will need to be removed, and Qt 6 looked like the perfect time to do so.
Why not add some generic Container::to<OtherContainer>() functions?
As I said, converting between containers is, in and of itself, a sketchy operation. There's a way to do so; why should we support them with extra convenience? Does the potential for good usage outweigh the potential for misusage? The data so far shows that no, it doesn't. But please, don't let me discourage you from contributing these functions to Qt!
Thank you for reading, and I'll see you next time.
About KDAB
Trusted software excellence across embedded and desktop platforms
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications. In addition to being leading experts in Qt, C++ and 3D technologies for over two decades, KDAB provides deep expertise across the stack, including Linux, Rust and modern UI frameworks. With 100+ employees from 20 countries and offices in Sweden, Germany, USA, France and UK, we serve clients around the world.
Hi, while some of the code snippets look interesting, all of this is still much more complicated to read and maintain than code using the simple "toSet()" function. For most code written in Qt, it is more important that the code CORRECT, than it is that the code is PERFORMANT. Forcing users to think about algorithms in code paths that are not performance critical IMO means premature optimization, which, as some people claim, is the root of all evil.
24 - Jun - 2021
Giuseppe D'Angelo
Hi,
let me try to counter-argument a bit, sorry for nitpicking on your answer. :)
Hi, while some of the code snippets look interesting, all of this is still much more complicated to read and maintain than code using the simple “toSet()” function.
I'd say that's partially true. However,
a solution like ranges::to or kdToContainer isn't that much more complicated or verbose to use (and please bear with me, if the documentation and Google hits hinted at those instead of toSet, you'd just be using those without wondering about "why there isn't a toSet() function?" -- I mean, is anyone asking why there isn't a "toStdDeque()"? (And, very ironically, there is a QList::toStdList()...)
If this is truly supposed to be a one-off in the code, then one can accept a tiny bit of extra verbosity.
If usages of toSet() aren't a one-off in the code, then this is no longer a matter of premature optimization; this is really optimization.
For most code written in Qt, it is more important that the code CORRECT, than it is that the code is PERFORMANT. Forcing users to think about algorithms in code paths that are not performance critical IMO means premature optimization
As I've tried to argue, this is a double edged sword, about the danger of offering an easy-to-misuse API.
First and foremost, offering the convenience is, well, convenient; the downside is that such convenience gets used even in code that is supposed to be performant.
Second, this risks bringing in a "death by thousand cuts". There isn't just premature optimization; there's also premature pessimization, caused by making a poor choice whereas a better one would've costed maybe 5 more seconds of typing. The danger of premature pessimization is that if one has slightly inefficient code all over the place, there is absolutely no easy way for someone to improve the overall performances of an application; a profiler will not be able to pinpoint a few easy problems to fix. That's because the inefficiency, eventually, becomes so thinly spread that finding the causes and fixing them becomes a super-massive investment (e.g. go around and change all of your container classes, string classes, and so on). I'd rather take the extra second before making a "worse" decision every time, rather than having to revisit the code N years down the line in a desperate search on how we can make the application boot in 1 second instead of 5.
In a nutshell: the ultimate goal would be that the default easy-to-go choice should also be the most efficient one; that's very hard to do with a "blatant", possibly non-efficient choice available immediately.
(Eventually, if one does down this path, this leads me to question whether Qt users would be better served by using Python or other languages where these conversion facilities are "first-class", rather than C++? It's an important question to ask, for Qt's own survival.)
Thanks for the comment!
24 - Jun - 2021
David Nolden
Thanks for the reply.
It's interesting that you bring in python.
IMO, one of the great advantages of Qt (especially it's container classes) is/was, that they are much easier to use than STL counterparts (at the price of less flexibility), and they can be programmed with a similar convenience as when programming in python. We're operating on a continuum here, from best to worst performance, and worst to best convenience:
* A custom container class optimized with custom algorithms for your specific usecase (most performant, least convenient)
* STL containers using STL algorithm and all the template specialization possibilities (performant, more convenient)
* Qt containers (less performant, even more convenient)
* python (worst performance, highest convenience, bad long-term maintainability)
It should be everybody's own choice which level of performance and convenience they want to use, but an advantage of CONVENIENT Qt containers is, that you can get the best from both worlds: the convenience of python, but the performance of C++, if you can find the performance critical paths and optimize them at a later point.
25 - Jun - 2021
Giuseppe D'Angelo
Hi,
Well, in the blog post itself I specifically argue that yes, Qt containers do offer some extra convenience, and I'm completely fine with it. What I'm less fine with is when this extra convenience starts falling on the verge of "easy to use, easy to misuse". Something like indexOf() doesn't fall there, something like toSet() does (IMNSHO). The very fact that there's a lot of reactions to its deprecation is also somehow a warning sign to me: does it mean that, in most codebases, it wasn't a one-off usage that can be quickly fixed -- and move on? Does it mean that it was extensively used? Doesn't that warrant that the code should be at least checked for its algorithmic characteristics?
Aside: the "bad long-term maintainability" of Python is a case of [citation needed]. And, "if you can find the performance critical paths and optimize them at a later point" is a tremendous effort. As I said in my comment before: if most of your code is ever so slightly "slow", you won't find them easily. This has historically led to quite some drastic decisions, like rewriting everything in another framework or another programming language. Do you want a data point? Qt for MCU, in order to cope with the small CPU/memory requirements, has a complete reimplementation of the Qt string/container classes, to be more efficient. That's because it's not about ONE specific container instance or string instance, it's about the fact that ALL of them are slightly slow (because their implementation in "mainstream" Qt is). I fail too see why non-MCU users should not get the same benefits. (Ok, now the discussion is completely derailed, apologies. :-))
Thanks again,
23 - Jun - 2021
David Nolden
Some say that "premature optimization is the root of all evil", and by forcing people to think about algorithms and use more complex code, you're forcing them to do exactly that.
30 - Jun - 2021
Ilya
Why no list.uniqu() to ease the migration from all the toSet().toList() ? That would be very convenient as it allows for bulk search & replace
30 - Jun - 2021
André Somers
Others say that algorithms express intent much better than all the ways people invent to avoid them. More expressive code is easier to read and easier to maintain, contains less bugs and generally is faster.
27 - Jul - 2021
Tired Today
Giuseppe, you said that the Qt policy is to have nice-to-use APIs, and yet you are here defending that people should use a non-discoverable (no auto-completion), much larger and error prone to use API. See the huge contradiction?
The only way in which this would not be contradictory is because, as you said, you are a C++ teacher. You know what? Many people are not with Qt because C++, but in spite of C++.
You will not drive people from C++ to Qt, but you will drive Qt people away from Qt with this kind of changes.
27 - Jul - 2021
Giuseppe D'Angelo
Hi!
Giuseppe, you said that the Qt policy is to have nice-to-use APIs, and yet you are here defending that people should use a non-discoverable (no auto-completion), much larger and error prone to use API. See the huge contradiction?
Well, I also said that nice-to-use shouldn't clash with easy-to-misuse. I do see the struggle, and find hard to draw the line. And I don't claim I have all the answers. Thus, this blog post. :)
The only way in which this would not be contradictory is because, as you said, you are a C++ teacher. You know what? Many people are not with Qt because C++, but in spite of C++.
Well, this opens an interesting conversion. For instance, would users prefer to use Qt combined with another language (Python)?
2 - Aug - 2021
round earther
Have i read this correctly? Are you suggesting I replace toSet with
This sounds like an over-simplification of my argument, given the code you pasted doesn't even do the same thing; so I'm not sure what you meant there...?
PS: I am serious, and don't call me Shirley.
21 - Aug - 2021
Tristan Lewis
Thanks for making these kinds of performance improving choices! We're working with an older code base that needs performance improvements and is suffering from "death by a thousand cuts" issues.
These kinds of changes, along with articles like this, help give us a better idea of where to head!
22 - Aug - 2021
Giuseppe D'Angelo
Thanks, glad you liked it.
20 - Dec - 2021
Doug Rogers
You could use these functions to replace the Qt functions
Hi. Took the liberty of fixing formatting in the above, as the blog ate some code parts.
Sure, the above "works". But:
1) fromList is such a generic term that shouldn't be "reserved" by something that makes QSets out of it. Nowhere in its name it says that it's building QSets.
2) Again this funcionality is packaged in a much more generic and convenient form by ranges-v3, kdToContainer, and so on.
QList<int> myList = ~~~;
auto set1 = myList | kdToContainer<QSet>();
3) I still struggle to understand why this specific functionality is much wanted. What is it about conversions from/to sets that deserves their own? Why not generic conversions?
4) The signatures above are actually wrong -- so don't use that snippet as-is.
13 - Oct - 2022
Pavel Celba
I disagree that std::sort() + std::unique() is best way - for any large data set - e.g. 100M entries. I'd say most performant will still be myList.toSet().toList(); for making it unique as it's O(N) algorithm compared to O(N * log N) which you need for sorting. Memory wise, it will not be best, but if you are interested in performance.
Or do you have hard data that it's the other case???
13 - Oct - 2022
Giuseppe D'Angelo
Hi,
Please be very aware of the hidden constants in the big-O notation, as well as the hidden amortized costs. Yes, creating a set from a sequence costs O(N) (but nitpicking: in the optimal case where all insertions are O(1). If there a lot of duplicates, insertions start costing a linear amount of time, possibly up to O(N²)). Hidden in that O(1) insertion cost there is a huge constant: the cost of allocating a piece of memory per element (at least that's how QSet works in Qt 5, similarly to std::unordered_set). I'm not talking about the memory usage, I'm talking about CPU time. This cost (CPU time for O(N) allocations, O(N) deallocations) quickly trumps the sort+unique approach. For instance: https://quick-bench.com/q/Dk0bWszN-DVApEdcm4hZhS8-I2o
Giuseppe D’Angelo
Senior Software Engineer
Senior Software Engineer at KDAB. Giuseppe is a long-time contributor to Qt, having used Qt and C++ since 2000, and is an Approver in the Qt Project. His contributions in Qt range from containers and regular expressions to GUI, Widgets, and OpenGL. A free software passionate and UNIX specialist, before joining KDAB, he organized conferences on opensource around Italy. He holds a BSc in Computer Science.
Our hands-on Modern C++ training courses are designed to quickly familiarize newcomers with the language. They also update professional C++ developers on the latest changes in the language and standard library introduced in recent C++ editions.
17 Comments
23 - Jun - 2021
David Nolden
Hi, while some of the code snippets look interesting, all of this is still much more complicated to read and maintain than code using the simple "toSet()" function. For most code written in Qt, it is more important that the code CORRECT, than it is that the code is PERFORMANT. Forcing users to think about algorithms in code paths that are not performance critical IMO means premature optimization, which, as some people claim, is the root of all evil.
24 - Jun - 2021
Giuseppe D'Angelo
Hi,
let me try to counter-argument a bit, sorry for nitpicking on your answer. :)
I'd say that's partially true. However,
a solution like
ranges::to
orkdToContainer
isn't that much more complicated or verbose to use (and please bear with me, if the documentation and Google hits hinted at those instead oftoSet
, you'd just be using those without wondering about "why there isn't a toSet() function?" -- I mean, is anyone asking why there isn't a "toStdDeque()
"? (And, very ironically, there is aQList::toStdList()
...)If this is truly supposed to be a one-off in the code, then one can accept a tiny bit of extra verbosity.
If usages of
toSet()
aren't a one-off in the code, then this is no longer a matter of premature optimization; this is really optimization.As I've tried to argue, this is a double edged sword, about the danger of offering an easy-to-misuse API.
First and foremost, offering the convenience is, well, convenient; the downside is that such convenience gets used even in code that is supposed to be performant.
Second, this risks bringing in a "death by thousand cuts". There isn't just premature optimization; there's also premature pessimization, caused by making a poor choice whereas a better one would've costed maybe 5 more seconds of typing. The danger of premature pessimization is that if one has slightly inefficient code all over the place, there is absolutely no easy way for someone to improve the overall performances of an application; a profiler will not be able to pinpoint a few easy problems to fix. That's because the inefficiency, eventually, becomes so thinly spread that finding the causes and fixing them becomes a super-massive investment (e.g. go around and change all of your container classes, string classes, and so on). I'd rather take the extra second before making a "worse" decision every time, rather than having to revisit the code N years down the line in a desperate search on how we can make the application boot in 1 second instead of 5.
In a nutshell: the ultimate goal would be that the default easy-to-go choice should also be the most efficient one; that's very hard to do with a "blatant", possibly non-efficient choice available immediately.
(Eventually, if one does down this path, this leads me to question whether Qt users would be better served by using Python or other languages where these conversion facilities are "first-class", rather than C++? It's an important question to ask, for Qt's own survival.)
Thanks for the comment!
24 - Jun - 2021
David Nolden
Thanks for the reply.
It's interesting that you bring in python.
IMO, one of the great advantages of Qt (especially it's container classes) is/was, that they are much easier to use than STL counterparts (at the price of less flexibility), and they can be programmed with a similar convenience as when programming in python. We're operating on a continuum here, from best to worst performance, and worst to best convenience: * A custom container class optimized with custom algorithms for your specific usecase (most performant, least convenient) * STL containers using STL algorithm and all the template specialization possibilities (performant, more convenient) * Qt containers (less performant, even more convenient) * python (worst performance, highest convenience, bad long-term maintainability)
It should be everybody's own choice which level of performance and convenience they want to use, but an advantage of CONVENIENT Qt containers is, that you can get the best from both worlds: the convenience of python, but the performance of C++, if you can find the performance critical paths and optimize them at a later point.
25 - Jun - 2021
Giuseppe D'Angelo
Hi,
Well, in the blog post itself I specifically argue that yes, Qt containers do offer some extra convenience, and I'm completely fine with it. What I'm less fine with is when this extra convenience starts falling on the verge of "easy to use, easy to misuse". Something like
indexOf()
doesn't fall there, something liketoSet()
does (IMNSHO). The very fact that there's a lot of reactions to its deprecation is also somehow a warning sign to me: does it mean that, in most codebases, it wasn't a one-off usage that can be quickly fixed -- and move on? Does it mean that it was extensively used? Doesn't that warrant that the code should be at least checked for its algorithmic characteristics?Aside: the "bad long-term maintainability" of Python is a case of [citation needed]. And, "if you can find the performance critical paths and optimize them at a later point" is a tremendous effort. As I said in my comment before: if most of your code is ever so slightly "slow", you won't find them easily. This has historically led to quite some drastic decisions, like rewriting everything in another framework or another programming language. Do you want a data point? Qt for MCU, in order to cope with the small CPU/memory requirements, has a complete reimplementation of the Qt string/container classes, to be more efficient. That's because it's not about ONE specific container instance or string instance, it's about the fact that ALL of them are slightly slow (because their implementation in "mainstream" Qt is). I fail too see why non-MCU users should not get the same benefits. (Ok, now the discussion is completely derailed, apologies. :-))
Thanks again,
23 - Jun - 2021
David Nolden
Some say that "premature optimization is the root of all evil", and by forcing people to think about algorithms and use more complex code, you're forcing them to do exactly that.
30 - Jun - 2021
Ilya
Why no list.uniqu() to ease the migration from all the toSet().toList() ? That would be very convenient as it allows for bulk search & replace
30 - Jun - 2021
André Somers
Others say that algorithms express intent much better than all the ways people invent to avoid them. More expressive code is easier to read and easier to maintain, contains less bugs and generally is faster.
27 - Jul - 2021
Tired Today
Giuseppe, you said that the Qt policy is to have nice-to-use APIs, and yet you are here defending that people should use a non-discoverable (no auto-completion), much larger and error prone to use API. See the huge contradiction?
The only way in which this would not be contradictory is because, as you said, you are a C++ teacher. You know what? Many people are not with Qt because C++, but in spite of C++.
You will not drive people from C++ to Qt, but you will drive Qt people away from Qt with this kind of changes.
27 - Jul - 2021
Giuseppe D'Angelo
Hi!
Well, I also said that nice-to-use shouldn't clash with easy-to-misuse. I do see the struggle, and find hard to draw the line. And I don't claim I have all the answers. Thus, this blog post. :)
Well, this opens an interesting conversion. For instance, would users prefer to use Qt combined with another language (Python)?
2 - Aug - 2021
round earther
Have i read this correctly? Are you suggesting I replace toSet with
std::sort(container.begin(), container.end()); container.erase(std::unique(container.begin(), container.end()), container.end());
I dont want c++ garbage in my beautiful qt code
You cant be serious
5 - Aug - 2021
Giuseppe D'Angelo
Hi!
This sounds like an over-simplification of my argument, given the code you pasted doesn't even do the same thing; so I'm not sure what you meant there...?
PS: I am serious, and don't call me Shirley.
21 - Aug - 2021
Tristan Lewis
Thanks for making these kinds of performance improving choices! We're working with an older code base that needs performance improvements and is suffering from "death by a thousand cuts" issues.
These kinds of changes, along with articles like this, help give us a better idea of where to head!
22 - Aug - 2021
Giuseppe D'Angelo
Thanks, glad you liked it.
20 - Dec - 2021
Doug Rogers
You could use these functions to replace the Qt functions
20 - Dec - 2021
Giuseppe D'Angelo
Hi. Took the liberty of fixing formatting in the above, as the blog ate some code parts.
Sure, the above "works". But:
1)
fromList
is such a generic term that shouldn't be "reserved" by something that makes QSets out of it. Nowhere in its name it says that it's building QSets.2) Again this funcionality is packaged in a much more generic and convenient form by ranges-v3, kdToContainer, and so on.
3) I still struggle to understand why this specific functionality is much wanted. What is it about conversions from/to sets that deserves their own? Why not generic conversions?
4) The signatures above are actually wrong -- so don't use that snippet as-is.
13 - Oct - 2022
Pavel Celba
I disagree that std::sort() + std::unique() is best way - for any large data set - e.g. 100M entries. I'd say most performant will still be myList.toSet().toList(); for making it unique as it's O(N) algorithm compared to O(N * log N) which you need for sorting. Memory wise, it will not be best, but if you are interested in performance.
Or do you have hard data that it's the other case???
13 - Oct - 2022
Giuseppe D'Angelo
Hi,
Please be very aware of the hidden constants in the big-O notation, as well as the hidden amortized costs. Yes, creating a set from a sequence costs O(N) (but nitpicking: in the optimal case where all insertions are O(1). If there a lot of duplicates, insertions start costing a linear amount of time, possibly up to O(N²)). Hidden in that O(1) insertion cost there is a huge constant: the cost of allocating a piece of memory per element (at least that's how QSet works in Qt 5, similarly to std::unordered_set). I'm not talking about the memory usage, I'm talking about CPU time. This cost (CPU time for O(N) allocations, O(N) deallocations) quickly trumps the sort+unique approach. For instance: https://quick-bench.com/q/Dk0bWszN-DVApEdcm4hZhS8-I2o