Comment by krona
16 hours ago
> So the standard library plays it safe: if your move constructor might throw (because you didn’t mark it noexcept), containers just copy everything instead. That “optimization” you thought you were getting? It’s not happening.
This is a bit of a footgun and clang-tidy has a check for it: performance-noexcept-move-constructor. However, I don't think it's enabled by default!
Throwing move is super weird too. I believe that it was a mistake to not treat user move like C++11 destructors and default to noexcept(true) on them. But it is what it is.
On the other hand, writing special member functions at all(move & copy constructor/assignment, destructor) is a smell for types that don't just manage the lifetime of an object(unique_ptr like things). People should not generally be writing them and being open to the mistake of getting noexcept wrong.
The reason performance-noexcept-move-constructor is not enabled by default is likely because blindly applying noexcept is dangerous if the underlying logic isn't actually exception-free. If you let clang-tidy slap noexcept on a move constructor that does end up throwing (perhaps because it calls into a legacy member or allocates memory internally), the runtime behavior changes from caught exception to std::terminate().
The documentations seems to say that option only causes the compiler to issue a warning when move constructors are not marked noexcept - it doesn't override anything.
https://clang.llvm.org/extra/clang-tidy/checks/performance/n... constructor.html
Note that the way std::vector (and other STL containers) require noexcept move constructors for reallocation is by using template matching, and of course any other code might be doing this too, so having a compiler option that forced a constructor (or anything) to have a type signature different than the way it was declared would be a pretty dangerous thing to do since it'd be hard to know what the consequences would be.
I would argue performance-noexcept-move-constructor should always be on. Move constructors should almost always be noexcept since they typically just move pointers around and don't do allocations normally.
eh, depends. for instance think about a small_vector or small_string
1 reply →
clang-tidy checks but doesn't change things for you.
Since you can also put noexcept(false) to indicate something throws exceptions and you didn't just forget to mark it noexcept, it's not a bad policy to say every move constructor should have a noexcept marker.
Exceptions should never be enabled by default. We live in a 64bit world so allocations failing indicates some other problem.
What does processor but width have to do with the likelihood of allocation failures?
2 replies →
Exceptions can be used to indicate many kinds of errors, not just allocation failures.
Most sensible Compiler flags aren't enabled by default... I keep a list of arguments for gcc to make things better, but even then you'll also wanna use a static analysis tool like clang-tidy
Would you mind sharing your list?
performance-noexcept-move-constructor is great but it also complains about move assignment operators, which are completely different beasts and are practically impossible to make noexcept if your destructors throw.
If that's the issue you're facing, consider clang-query, e.g.: https://godbolt.org/z/bfG94qGan
You can put extra constraints on the caller if you'd like (e.g., isInStdNamespace()), though it's less trivial. Happy to help write something if you have a precise idea of what you want to match.
Throwing destructors will generally end in termination of the program if they are used as class members. Types like scope_exit are fine, but anywhere else will probably have noexcept(true) on it's destructor.
If I'm not mistaken, all the pitfalls in the article have clang-tidy lints to catch
Nothing about clang-tidy is enabled by default, and getting it to run at all in realistic projects is quite a chore.