c++stdcomparator

Does it make any sense to define operator< as noexcept?


I know that it makes perfect sense to define e.g. move constructor as noexcept (if possible) and I think I understand the effect of that.

But I have never seen a similar discussion about operator<() of a type used e.g. in std::set<>.

Does also non-throwing comparator have some (potential) optimizing effect (when used in std::set<>, std::map<>, std::sort() and similar)?


Solution

  • A move constructor is somewhat special, because there is an obvious fallback. In a situation where you cannot allow a move to throw an exception you can call a noexcept move constructor and when the move constructor is not noexcept you can fallback to a copy. Hence, declaring the move constructor that does not throw exceptions as noexcept is a potential optimization.


    For example std::vector::push_back does try to give a strong exception guarantee (from cppreference):

    If an exception is thrown (which can be due to Allocator::allocate() or element copy/move constructor/assignment), this function has no effect (strong exception guarantee).

    However, since C++11:

    If T's move constructor is not noexcept and T is not CopyInsertable into *this, vector will use the throwing move constructor. If it throws, the guarantee is waived and the effects are unspecified.

    Remember that pushing an element may require reallocations, ie to copy/move all elements to different memory location.

    The above means: If the type is CopyInsertable, ie one has a choice between copying and moving, the vector will move them when the move constructor is noexcept. In this case you can gain performance by declaring the move constructor noexcept.

    When the element type cannot be copied the move constuctor is used in any case (and the vector relaxes its exception guarantee, thanks to François Andrieux for making me aware of insert with similar balancing of exception safety vs performance). In this case what you gain by a noexcept move constructor is a stronger exception guarantee.


    std::vector::push_back and similar examples is why there is so much discussion about declaring a move constructor noexcept.

    For < there is no such obvious fallback that would be more expensive when < is not noexcept. Either you can call < or you can't.

    Otherwise < has the same advantages as any other noexcept method, that the compiler knows it does never throw (and if it does std::terminate is called, but the usual stack unwinding does not necessarily take place).