Noexcept affects libstdc++’s unordered_set
quuxplusone.github.io
quuxplusone.github.io
In fact most c++ developers believe that throwing an exception in a noexcept function is undefined behavior. It is not. The behavior is defined to call std::terminate. Which would lead one to ask how does it know to call that. Because noexcept functions have a hidden try catch to see if it should call it. The result is that noexcept can hurt performance, which is surprising behavior. C++ is just complicated.
In the end what got into the standard is that it is well-defined: it calls std::terminate. If I recall that change wasn't made without drama.
Up to 2011:
https://akrzemi1.wordpress.com/2011/06/10/using-noexcept/
As of '17:
https://devblogs.microsoft.com/oldnewthing/20180928-00/?p=99...
(I think this is the final word; I don't see any changes to exceptions in high-level release notes for c++20 and c++23 -- did I miss anything?)
On the other hand, the guarantee given by noexcept can enable optimizations in the caller.
This was the entire point of noexcept versus the throw() specifier...
I also find it difficult to conceive of a case where adding noexcept would lead to slower/longer code, other than arbitrary noexcept overloads such as TFA.
The usual alternative is to have a factory function that returns std::optional<T> or std::expected<T, Err>. This avoids two-stage init, but has other tradeoffs.
Later you get into another whole debate about movable types and what T should be once it's moved from (for example if you want to turn that std::optional<T> into a std::shared_ptr<T>), if it only has non-trivial constructors. An idea that just popped into my head would be to have a T constructor from std:optional<T>&&, which moves from it and resets the optional. But then it's not a real move constructor. With move constructors, a lot of times you just have to accept they'll have to leave the other object in some sort of empty or invalid state.
I'm not saying it's perfect but it's better than dealing with C++ exceptions. At least with error codes you can manually do some printing to find out what went wrong. With C++ exceptions you don't even get a line number or a stack trace.
You also don’t have to make the anemic constructor public.
Small but critical correction: noexcept functions can throw exceptions. What they cannot do is allow exceptions to bubble up to function invokers. This means that it is trivial to define noexcept functions: just add a try-catch block that catches all exceptions.
I think you didn't understood what I said.
In C++ functions declared as noexcept are expected to not allow exceptions to bubble up. If an exception bubbles up from one of these functions, the runtime calls std::terminate.
This does not mean you cannot throw and handle exceptions within such a function. You are free to handle any exception within a noexcept function. You can throw and catch as many exceptions you feel like it while executing it. You just can't let them bubble up from the scope of your function.
No. There are comments in this thread from people who are surprised that you can still handle exceptions within a noexcept function. Some seem to believe noexcept is supposed to mean "don't use exception within this scope". My comment is intended to clarify that, yes, you can throw and catch any exception from within a noexcept function, because noexcept does not mean "no exceptions within this scope" and instead only means "I should not allow exceptions to bubble up, and if I happen to do then just kill the app".
Not wisdom at all, just a very basic and predictable failsafe.
If a function that is declared to not throw any exception happens to throw one, the runtime handles that scenario as an eggregious violation of its contract. Consequently, as it does with any malformed code, it terminates the application.
C++ should never have had them, but we have sane clean C++ now. It’s called Rust.
I agree that Rust's macros are annoying. I think it was a mistake to invent an entirely different language and syntax for it. Of course Rust also has procedural macros, which are macros written in Rust. IMHO that's how they should all work. Secondary languages explode cognitive load.
Even procedural macros are annoying, though. You need to make a separate crate for them. You still need to write fully-qualified everything, even standard functions, for hygiene reasons. Proc macros that actually do, erm, macro operations and produce a lot of code cause Rust's language server to grind to a halt in my experience. You're effectively writing another language that happens to share a lexer with Rust (what's the problem with that? Well, if I'd known that I'd need another language to solve my problem I might not have chosen Rust...).
If static reflection does indeed land on C++26, this experience will be even better.
The only difference is that Rust has better syntactic sugar for the latter, but Result is really isomorphic to Java checked exceptions.
Panic could be said to be the same as an unchecked exception, except you have a lot more control on what causes them. The panic you get from calling unwrap() on an Option is the same as a NullPointerException, but you have full control on which points of the program that can generate it.
https://doc.rust-lang.org/1.80.1/src/alloc/vec/mod.rs.html#1...
Rust Result is great. I love it.
The root article was talking about C++. Rust panic is basically the same as a C++ exception afaict. With the caveat that Rust discourages catching and resuming from panics. But you can!
Legacy will affect rust too. It's not better, just younger.
I tend to think of coding languages as laws passed by parliaments. A lot of legacy laws, and regulations hang around pver time.
This is to be expected, in fact Rust has to be a lot better to even make a showing, because C is the "default" in some sense, you can't just be similarly good, you have to be significantly better for people to even notice.
I expect that long before 2050 there will be other, even better languages, which learn from not only the mistakes Rust learned from, but the mistakes in Rust, and in other languages from this period.
Take Editions. C was never able to figure out a way to add keywords. Simple idea, but it couldn't be done. They had to be kludged as magic with an underscore prefix to take advantage of an existing requirement in the language design, in C++ they decided to take the compatibility hit and invalidate all code using the to-be-reserved words. But in Rust they were able to add several new keywords, no trouble at all, because they'd thought about this and designed the language accordingly. That's what Editions did for them. You can expect future innovation along that dimension in future languages.
So while nice, they aren't really much better than traditional compiler switches for language editions.
> The outcome is that libstdc++’s unordered_set has performance characteristics that subtly depend on the true name and noexceptness of the hash function.
> - A user-defined struct H : std::hash<std::string> {} will see smaller allocations and more calls to the hash function, than if you had just used std::hash<std::string> directly. (Because std::hash<std::string> is on the blacklist and H is not.)
> - A user-defined hasher with size_t operator() const noexcept will see smaller allocations and more calls to the hash function (especially during rehashing). One with size_t operator() const will see larger allocations and fewer calls to the hash function.
I think this would be an ABI break.
So yeah, it is probably physically impossible for libstdc++ to "get on the ball" in the way I'd hoped. Darn.
I mean, of course you should follow this article's advice on noexcept, but there's a whole world of associative container performance optimizations out there.
I recently tried to compile Folly on Windows and gave up. Maybe Abseil is better? I’m not sure. But it’s still a HUGE dependency to take on.
conan and vcpkg come with their own can of worms and heartaches.
There are huge swaths of the C++ lands map I’ve not visited, so I’m sure there are areas where it wouldn’t be a good choice, but I personally think vcpkg has been great.
https://www.boost.org/doc/libs/develop/libs/unordered/doc/ht...
The chaining hashtable in liburcu is truly lock free, based on a real build system, and in my experience outperforms everything facebook and google have ever published: https://github.com/urcu/userspace-rcu
Abseil isn't too bad.
You don't have a leg to stand on here. I can build and run liburcu on everything. You can barely build Folly on FreeBSD...
std unordered map is slow by design for several reasons.
Things of interest are:
- open addressing method: linear, quadratic, robinhood, hybrid
- the default hash function
- the logic for the number of buckets (power of two, prime) and associated hash projection (fibonacci, modulo).
Boost seems to be doing a decent job at this now.