std::move_if_noexcept is similar to std::move but it returns rvalue reference only when the object has move constructor with noexcept marked.

When we use std::move, it returns a rvalue reference of the object and it invokes move constructor when this object is initialized or assigned to another same type object. When move happens, the target object becomes the source and source becomes empty.

Now, while moving, if an exception is thrown while the move is happening and the source became empty but target did not get source’s data, then we lost the source completely.

This could have been avoided if std::move has returned a lvalue reference instead of rvalue which would have called copy constructor.

This can be achieved using std::move_if_noexcept. If class have move constructor with noexcept marked, then this function would return rvalue reference of the object and would lead to use the move semantic. However if, the move constructor is not marked with noexcept, this function would return lvalue reference which would lead to use copy semantic.

For example:

#include <iostream>
 
void _throw()
{
    throw 1;
}
 
class Store
{
    int *ptr{};
 
public:
    Store() = default;
    Store(int value)
        : ptr{new int{value}}
    {
    }
 
    Store(const Store &store)
        : ptr{new int{*store.ptr}}
    {
    }
 
    Store(Store &&s)
        : ptr{s.ptr}
    {
        s.ptr = nullptr;
        _throw();
    }
 
    ~Store()
    {
        delete ptr;
    }
 
    friend std::ostream &operator<<(std::ostream &out, const Store &s)
    {
        return out << "Store(" << *s.ptr << ")";
    }
};
 
int main(int argc, char const *argv[])
{
    Store s{1};
    try
    {
        Store s2{std::move(s)};
        std::cout << s2 << '\n';
    }
    catch (...)
    {
        std::cout << "caught exception in main" << '\n';
    }
 
    std::cout << s << '\n';
    return 0;
}

Here, we have a Store class that supports both copy and move semantics. Move constructor of Store moves the object but throws an exception in the end. Let’s run this program and see the output:

caught exception in main
fish: Job 1, './move_if_noexcept.out' terminated by signal SIGSEGV (Address boundary error)

Let’s understand the code flow:

  1. We have created Store object s.
  2. Then in try block, we can initialize another Store object with std::move of s. It invokes the move constructor of Store which moves s object to s2.
  3. However, after the move, move constructor throws an exception.
  4. That exception then get caught by the catch-all block and we see the log.
  5. Then outside of try-catch block, we try to print s which uses overloaded operator<< function.
  6. This function tries to access the resource which is already moved and it tries to dereference a null pointer which gives crashes the program.

If we use std::move_if_noexcept function, then initialization of s2 would happen using copy constructor because that function would return a lvalue reference. Let’s update the program:

int main(int argc, char const *argv[])
{
    Store s{1};
    try
    {
        Store s2{std::move_if_noexcept(s)};
        std::cout << s2 << '\n';
    }
    catch (...)
    {
        std::cout << "caught exception in main" << '\n';
    }
 
    std::cout << s << '\n';
    return 0;
}

When we run this program, it works as expected:

Store(1)
Store(1)

References

  1. https://www.learncpp.com/cpp-tutorial/stdmove_if_noexcept/