为什么std::exchange不是noexcept

Why is std::exchange not noexcept?

本文关键字:noexcept 不是 exchange 为什么 std      更新时间:2023-10-16

根据标准(N4659,§23.2.4,[utility.exchange](,std::exchange应该做一个std::move和一个std::forward

template <class T, class U = T> T exchange(T& obj, U&& new_val);

效果:相当于:

T old_val = std::move(obj);
obj = std::forward<U>(new_val);
return old_val;

moveforward 都标记为 noexcept
(N4659, §23.2.5, [前进](:

template <class T> constexpr T&& forward(remove_reference_t<T>& t) noexcept; template <class T> constexpr T&& forward(remove_reference_t<T>&& t) noexcept;

返回: static_cast<T&&>(t) .

(...

template <class T> constexpr remove_reference_t<T>&& move(T&& t) noexcept;

返回: static_cast<remove_reference_t<T>&&>(t) .

那么为什么不exchange noexcept呢?这是否还有其他原因,还是委员会只是忽略了这一点?这是提议还是我可以写一个?

不像std::swap,默认情况下只依赖于移动构造函数,因此通常应该noexcept,如果需要复制新值,std::exchange可以分配资源。虽然当new_valU&&并且移动赋值和旧值的移动抛出时,条件noexcept可能会有一个复杂的表达式,但似乎没有人提出这样的建议<</p>

div class="answers>

代码还使用构造函数和类型T的赋值运算符。其中一个可能会抛出。

如果要使用非 std 版本来创建noexcept移动 ctors,仅使用仅使用与使用的值有条件noexcept的右值,则可以使用此版本:

namespace estd {
template<class T, class U = T, std::enable_if_t<
        std::is_move_constructible<std::remove_reference_t<T>>::value && 
        std::is_assignable<std::remove_reference_t<T>, std::remove_reference_t<U>&&>::value && 
        !std::is_lvalue_reference<U>::value,
    int> = 0    
>
T rval_exchange(T& obj, U&& new_value) noexcept(
    std::is_nothrow_move_constructible<std::remove_reference_t<T>>::value &&
    std::is_nothrow_assignable<std::remove_reference_t<T>, std::remove_reference_t<U>&&>::value
)
{
    T old_value {std::move(obj)};
    obj = std::move(new_value);
    return old_value;
}
} // namespace estd

如果使用左值引用作为要移入obj的值(即 new_value必须是绑定到右值的右值引用(,如果使用的类型 T 不可移动可构造,或者如果移动构造不适用于该右值。

这样,您可以定义 noexcept 移动 ctors。在这里演示。我之所以将函数限制为仅右值,是因为一般来说,复制并不noexcept,即使情况并非总是如此,并且因为exchange应该在移动 ctors 中使用,而没有它noexcept会使这些 ctor 不noexcept,这不好的原因很明显。

void do_stuff() noexcept
{ /*...*/ }
class Sample
{
    std::string mBody;
public:
    Sample(const std::string& body = ""): mBody {body} {}
    Sample(const Sample& s): mBody {s.mBody} {
        printf("%sn", __PRETTY_FUNCTION__); // noexcept
    }
    Sample& operator=(const Sample& s) { 
        mBody = s.mBody; 
        printf("%sn", __PRETTY_FUNCTION__); // noexcept
        return *this;
    }
    Sample(Sample&& dying) noexcept(
        noexcept(do_stuff()) && 
        noexcept(estd::rval_exchange(dying.mBody, {}))
    ):
        mBody {estd::rval_exchange(dying.mBody, {})}
    {
        do_stuff(); // noexcept
        printf("%sn", __PRETTY_FUNCTION__); // noexcept
    }
    Sample& operator=(Sample&& dying) noexcept(
        noexcept(do_stuff()) && 
        noexcept(estd::rval_exchange(dying.mBody, {}))
    )
    {
        mBody = estd::rval_exchange(dying.mBody, {});
        do_stuff();
        printf("%sn", __PRETTY_FUNCTION__); // noexcept
        return *this;
    }
    std::string body() const noexcept {return mBody;}
};
int main()
{
    std::cout << std::boolalpha;
    Sample rval{"wow such string very content"};
    Sample dummy;
    std::cout << noexcept( Sample(std::move(rval)) ) << std::endl; // prints true
    std::cout << noexcept( dummy = std::move(rval) ) << std::endl; // prints true
    // The rest only to show that move semantics actually work
    Sample f (std::move(rval));             // Calls move ctor
    std::cout << rval.body() << std::endl;  // prints empty line, empty string moved in rval
    std::cout << f.body() << std::endl;     // prints wow such string very content
    // estd::rval_exchange(f, rval); // Fails to compile bc rval is an lvalue reference, template disabled
    std::cout << (estd::rval_exchange(f, std::move(rval))).body() << std::endl; 
    // Ok, calls move ctor and move assignment (in estd::rval_exchange) and 
    // prints wow such string very content
    std::cout << f.body() << std::endl; // empty line, rval (empty) moved in f
    std::cout << "end" << std::endl;
}

C++23 通过向std::exchange添加条件noexcept规范来解决此问题 - 请参阅P2401R0