Comment by krona

Comment by krona 17 hours ago

21 replies

> 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!

beached_whale 10 hours ago

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.

Fiveplus 16 hours ago

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

  • HarHarVeryFunny 12 hours ago

    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.

  • dbcpp 13 hours ago

    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.

    • [removed] 13 hours ago
      [deleted]
    • jcelerier 11 hours ago

      eh, depends. for instance think about a small_vector or small_string

      • dbcpp 11 hours ago

        True, in that case it should just adopt the noexcept status of the object it holds.

  • immibis 15 hours ago

    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.

  • phkahler 15 hours ago

    Exceptions should never be enabled by default. We live in a 64bit world so allocations failing indicates some other problem.

    • zbentley 14 hours ago

      What does processor but width have to do with the likelihood of allocation failures?

      • HarHarVeryFunny 14 hours ago

        I think what he means is that on a 64-bit system you have a massive virtual address space (typically only 48-bit, but that's still 256TB), and since malloc allocates from virtual address space, not limited by physical memory, it is unlikely you will get a malloc failure (unless you are trying to allocate more than 256TB per process, maybe due to a memory leak).

      • petcat 14 hours ago

        640K ought to be enough for anybody!

    • usefulcat 8 hours ago

      Exceptions can be used to indicate many kinds of errors, not just allocation failures.

juliangmp 16 hours ago

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

rfc3092 12 hours ago

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.

  • dataflow 10 hours ago

    If that's the issue you're facing, consider clang-query, e.g.: https://godbolt.org/z/bfG94qGan

      match cxxConstructExpr(hasDeclaration(cxxConstructorDecl(isMoveConstructor(), unless(isNoThrow())).bind("throwing-move")))
    
    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.
  • beached_whale 10 hours ago

    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.

    • [removed] 10 hours ago
      [deleted]
grogers 9 hours ago

If I'm not mistaken, all the pitfalls in the article have clang-tidy lints to catch

jeffbee 6 hours ago

Nothing about clang-tidy is enabled by default, and getting it to run at all in realistic projects is quite a chore.