为什么添加默认的移动赋值运算符会破坏标准交换函数的编译?

问题描述 投票:0回答:1

在以下代码中,如果未注释移动分配,则交换函数将停止程序的编译。我在所有 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
)。

我的问题是:为什么添加默认的特殊成员函数会阻止标准库将类型视为可移动?

c++ move-semantics raii
1个回答
0
投票

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
将使用移动赋值运算符,并且重载决策中不会有歧义。

© www.soinside.com 2019 - 2024. All rights reserved.