Does noexcept matter with explicitly defaulted move constructor/assignment operator?
Asked Answered
C

1

10
class C
{
    public:
        C(C&&) = default; // (1)
        C& operator=(C&&) = default; // (1)

        C(C&&) noexcept = default; // (2)
        C& operator=(C&&) noexcept = default; // (2)
}

As far as I know, if move constructor/assignment operator are either implicitly generated or explicitly defaulted by user (1), the compiler will decide whether these special member functions should be noexcept or not depending on whether all the members of a class provide noexcept guarantees for move operations. But what if I want to use default move special members and force them to be noexcept regardless of the underlying members exception guarantees for move operations? Is there any difference for the compiler between (1) and (2) declarations?

Chafe answered 15/1 at 6:10 Comment(1)
Yes it matters... moving with noexcept can be optimized by the compiler (specialy true when moving in containers like in std::sort)Externalize
C
10

Ok, I found the answer in Nico's Josuttis book "C++ Move Semantics - The Complete Guide":

When you have a defaulted special member function you can explicitly specify a different noexcept guarantee than the generated one. For example:

class C
{
  ...
 public:
  C(const C&) noexcept = default;     // guarantees not to throw (OK since C++20)
  C(C&&) noexcept(false) = default;   // specifies that it might throw (OK since C++20)
  ...
};

Before C++20, if the generated and specified noexcept condition contradict, the defined function was deleted.

I asked this question because I want STL algorithms to always apply move semantics to my classes regardless of their members' exception guarantees, and I am ready to deal with std::abort if something goes wrong. But it seems that prior to C++ 20 the only way to force noexcept guarantees on move special member functions was to explicitly define them, even if you don't need your own implementation. It turns out C++20 solved this by allowing us to specify noexcept guarantees for defaulted special member functions. Of course, as in case with any other noexcept function, if any of the members of a class throws during a move operation, std::abort will be called if move constructor/assignment operator are declared noexcept for that class, as it was kindly pointed out by @MichaëlRoy in the comments section.

Chafe answered 15/1 at 6:47 Comment(8)
@Alexey104. This means that for a move constructor, the default noexcept condition is equal to noexcept(noexcept(X::X(X&&)...); where X... are the types of the members of your class. So, if you specify noexcept, and one of you data members' constructor throws, your program will perform a hard exit. This definitely is worth mentioning in your answer, since before c++20, this kind of behavior was not possible.Respirator
I meant noexcept(noexcept(X::X(X&&) && ...) sorry for the typo.Respirator
@MichaëlRoy >"So, if you specify noexcept, and one of you data members' constructor throws, your program will perform a hard exit." Yes, I understand that, and I intentionally want this behavior. I want STL algorithms to always apply move semantics to my classes regardless of of their members' exception guarantees, and I accept std::abort if something goes wrong. Thank you for the clarification.Chafe
Perhaps mention this behavior in the answer for others reading this Q&A.Jimmie
@Chafe Cool. I pointed it out because I also think it's worth mentioning in your answer.Respirator
@MichaëlRoy, oh, I missed your last sentence, sorry.Chafe
If your constructor is noexcept(true), then any exception in any data member constructor will cause an abort,regardless of that particular constructor noexcept status.Respirator
Sure, that is exactly what I meant. Sometimes I just struggle to formulate the right sentence due to my bad English, but I am working on this.Chafe

© 2022 - 2024 — McMap. All rights reserved.