在以下代码中,如果未注释移动分配,则交换函数将停止程序的编译。我在所有 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 或概念来启用/禁用
std::swap
重载,并且当特殊函数未注释时,由于某种原因,某些特征会失败(libstdc++ 上的 is_move_constructible
和/或 is_move_assignable
)。
我的问题是:为什么添加默认的特殊成员函数会阻止标准库将类型视为可移动?
std::move
的主要实现在内部使用移动分配,类似于。
template <typename T>
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);
| ^
这两个运算符都可以使用 xvalue 进行调用,并且两者都不是更好的匹配。
std::swap
受到限制,因此只有 MoveAssignable 类型可以交换,而你的类型则不能。
即使你可以打电话
std::swap
,你也不能同时打电话
std::swap
的默认实现,它使用 operator=
operator=
定义
std::swap
这将是无限递归,因为
=
和 std::swap
的定义是循环的。
您可以为您的类型定义自定义
swap
,这样您就不再依赖 std::swap
。
只保留 operator=(test)
,看起来像:
test& operator=(test other) noexcept {
swap(*this, other); // custom function, not std::swap
return *this;
}
您还可以手动定义单独的
operator=(const test&)
和 operator=(test&&)
,以便 std::swap
将使用移动赋值运算符,并且重载决策中不会有歧义。