为什么基 class 中的复制和交换会导致复制赋值运算符在派生 class 中被隐式删除?

Why does copy-and-swap in a base class cause the copy-assignment operator to be implicitly deleted in the derived class?

仅在 GCC and Clang 中测试过,在基 class 中存在按值传递复制赋值运算符(在实现复制和交换(或复制和- move) 成语) 导致派生的 class 中的复制赋值运算符被隐式删除。

Clang 和 GCC 同意这一点; 为什么会这样?

示例代码:

#include <string>
#include <iostream>

struct base {
    base() {
        std::cout << "no-arg constructor\n";
    }
    base(const base& other) :
        str{other.str} {
        std::cout << "copy constructor\n";
    }
    base(base&& other) :
        str{std::move(other.str)} {
        std::cout << "move constructor\n";
    }
    base& operator=(base other) {
        std::cout << "copy assigment\n";
        str = std::move(other.str);
        return *this;
    }
    base& operator=(base&& other) {
        std::cout << "move assigment\n";
        str = std::move(other.str);
        return *this;
    }

    std::string str;
};

struct derived : base {
    derived() = default;
    derived(derived&&) = default;
    derived(const derived&) = default;
    derived& operator=(derived&&) = default;
    derived& operator=(const derived&) = default;
};

derived foo() {
    derived ret;
    ret.str = "Hello, world!";
    return ret;
}

int main(int argc, const char* const* argv) {

    derived a;
    a.str = "Wat";
    a = foo(); // foo() returns a temporary - should call move constructor
    return 0;
}

在您的代码中,未删除派生的复制分配。但是删除的是移动赋值,因为 [class.copy.assign]/7.4,它声明如果基 class 上的移动赋值的重载决策不明确,则删除默认的移动赋值运算符。

编译器无法判断是调用 operator=(base) 还是 operator=(base&&) 来移动基数 class.


这始终是个问题,即使您尝试将一个基础 class 对象直接移动到另一个基础 class 对象。所以同时拥有两个重载是不切实际的。我不清楚为什么你需要两者。据我所知,您可以删除 operator=(base&&) 过载而不会产生不良影响。

[class.copy.assign]/7 A defaulted copy/move assignment operator for class X is defined as deleted if X has:
(7.4) - ...a direct base class M that cannot be copied/moved because overload resolution (16.3), as applied to find M’s corresponding assignment operator, results in an ambiguity...

base的移动分配不明确;它有两个赋值运算符都接受一个右值。观察这个 doesn't compile:

base a, b;
a = std::move(b);

因此,derived 的移动分配最终定义为已删除。