提问人:Xeverous 提问时间:9/15/2023 最后编辑:Xeverous 更新时间:9/15/2023 访问量:77
为什么添加默认的移动赋值运算符会破坏标准交换函数的编译?
Why adding a defaulted move assignment operator breaks compilation of standard swap function?
问:
在下面的代码中,如果未注释移动赋值,则交换函数将停止程序的编译。我在所有 3 个主要编译器(GCC、Clang、MSVC)上都观察到了这种行为。
#include <utility>
#include <memory>
struct test
{
test() = default;
test(test&& other) noexcept = default;
//test& operator=(test&& other) noexcept = default;
test(const test& other)
: ptr(std::make_unique<int>(*other.ptr))
{}
test& operator=(test other) noexcept
{
std::swap(*this, other);
return *this;
}
std::unique_ptr<int> ptr;
};
Godbolt 测试:https://godbolt.org/z/v1hGzzEaz
在研究标准库实现时,它们使用 SFINAE 或概念来启用/禁用重载,并且当特殊函数未注释时,由于某种原因,某些特征会失败(和/或在 libstdc++ 上)。std::swap
is_move_constructible
is_move_assignable
我的问题是:为什么添加默认的特殊成员函数会阻止标准库将类型视为可移动的?
编辑 1:事实证明,问题在于 x 值在 和 之间的重载解析中没有偏好(这会导致不明确的重载错误),因此标准库特征无法将类型识别为可移动的。T
T&&
编辑 2:重要提示:请注意,示例代码不是复制和交换习语的正确实现。它应该使用自定义交换函数或使用所有 5 个特殊成员函数来完成。当前实现创建移动/复制分配和交换调用的无限递归。
答:
的主要实现在内部使用移动赋值,如下所示:std::swap
template <typename T>
requires std::is_swappable_v<T> // exposition-only, constraint might be different
void swap(T& a, T& b) {
T c = std::move(a);
a = std::move(b);
b = std::move(c);
}
这意味着移动分配需要对您的类型有效,但事实并非如此。 如果要调用移动赋值运算符,则会出现错误:
<source>:18:15: error: use of overloaded operator '=' is ambiguous [...]
[...] |
<source>:9:11: note: candidate function
9 | test& operator=(test&& other) noexcept = default;
| ^
<source>:15:11: note: candidate function
15 | test& operator=(test other);
| ^
std::swap
受到约束,因此只能交换 MoveAssignable 类型,而不能交换您的类型。
两个运算符都可以用 xvalue 调用,并且两者都不是更好的匹配。
这是因为只有左值到右值的转换会延长转换序列,并且从 xvalue 初始化对象被认为是“自由的”(例如,这不是转换,也不比绑定引用差)。=
即使你可以打电话,你也不能同时打电话std::swap
- 依赖 的默认实现,它使用
std::swap
operator=
- 根据
operator=
std::swap
这将是无限递归,作为 和 的定义是循环的。=
std::swap
解决 方案
您可以为您的类型定义自定义,以便不再依赖 .
仅保留 ,如下所示:swap
std::swap
operator=(test)
friend void swap(test& a, test& b) noexcept {
using std::swap; // Use swap() functions found through ADL, fall back
swap(a.ptr, b.ptr); // onto std::swap if there are none.
} // (not necessary if you only have a std::unique_ptr member,
// you could just use std::swap(a.ptr, b.ptr)).
test& operator=(test other) noexcept {
swap(*this, other); // custom function, not std::swap
return *this;
}
您还可以单独和手动定义,以便使用移动赋值运算符,并且在重载解析中不会有歧义。operator=(const test&)
operator=(test&&)
std::swap
评论
const T &
T
T &&
评论
test& operator=(test other)
other
std::swap(*this, other);
std::swap(ptr, other.ptr);
operator=
noexcept
noexcept