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

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

提问人:SumDood 提问时间:12/21/2019 更新时间:12/26/2019 访问量:149

问:

仅在 GCCClang 中测试,基类中存在按值传递的复制赋值运算符(在实现复制和交换(或复制和移动)惯用语时很有用)会导致派生类中的复制赋值运算符被隐式删除。

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;
}
C++ C++17 已删除 和交换 默认函数

评论

1赞 David G 12/21/2019
operator=(base other)不是复制赋值运算符,它只是一个常规赋值运算符。复制赋值运算符仅作为参数。base const&
4赞 walnut 12/21/2019
@0x499602D2 否,这也被视为复制分配运算符,请参阅 eel.is/c++draft/class#copy.assign-1
0赞 David G 12/21/2019
@walnut 哦,有意思。
1赞 Igor Tandetnik 12/21/2019
will 的隐式定义通常会尝试调用 ,但该调用是不明确的,因为您在 中定义了两个赋值运算符,它们都接受 rvalue。derived& operator=(derived&& other)base::operator=(std::move(other))base
2赞 walnut 12/21/2019
删除的不是复制分配,而是移动分配,请参阅您的 Clang 链接。(GCC 链接根本不提供任何诊断。

答:

4赞 walnut 12/21/2019 #1

在代码中,不会删除派生的副本赋值。但是,由于 [class.copy.assign]/7.4 的原因,删除的是移动赋值,该赋值指出,如果基类上移动赋值的重载解析不明确,则删除默认的移动赋值运算符。

编译器无法判断是调用还是移动基类。operator=(base)operator=(base&&)


这始终是一个问题,即使您尝试将一个基类对象直接分配给另一个基类对象也是如此。因此,同时具有两个重载并不实际。我不清楚为什么两者都需要。据我所知,您可以消除过载而不会产生不良影响。operator=(base&&)

3赞 Igor Tandetnik 12/21/2019 #2

[类.copy.assign]/7如果具有以下条件,则类的默认复制/移动赋值运算符被定义为已删除:
(7.4) - ...无法复制/移动的直接基类,因为应用于 find 的相应赋值运算符的重载解析 (16.3) 会导致歧义......
XXMM

base的移动分配模棱两可;它有两个赋值运算符,都接受 rvalue。请注意,这不会编译

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

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

评论

1赞 M.M 12/21/2019
同样相关的是“定义为已删除的默认移动赋值运算符被重载解析忽略”,因此 OP 的赋值语句使用复制赋值运算符,即使移动更匹配