错误:使用复制和交换习语的交换函数中“operator=”的不明确重载

error: ambiguous overload for 'operator=' in swap function using the copy-and-swap idiom

提问人:Dávid Tóth 提问时间:11/4/2019 更新时间:11/4/2019 访问量:197

问:

在具有常量引用作为成员的类中使用 copy-and-swap 习语时, 出现上述错误。

示例代码:

#include <iostream>
#include <functional>

using std::reference_wrapper;

class I_hold_reference;
void swap(I_hold_reference& first, I_hold_reference& second);

class I_hold_reference{
    inline I_hold_reference(const int& number_reference) : my_reference(number_reference){}
    friend void swap(I_hold_reference& first, I_hold_reference& second);
    inline I_hold_reference& operator=(I_hold_reference other){
        swap(*this, other);
        return *this;
    }
    inline I_hold_reference& operator=(I_hold_reference&& other){
        swap(*this, other);
        return *this;
    }
private:
    reference_wrapper<const int> my_reference;
};

void swap(I_hold_reference& first, I_hold_reference& second){
    first = I_hold_reference(second.my_reference); //error: use of overloaded operator '=' is ambiguous (with operand types 'I_hold_reference' and 'I_hold_reference')
}

当 Copy 赋值运算符更改为按引用而不是按值获取其参数时,错误将得到修复。

    inline I_hold_reference& operator=(I_hold_reference& other){ ... }

为什么这样可以修复错误? 一种可能的含义是,链接问题中引用的重要优化可能性丢失。对于参考资料来说,这是真的吗? 此更改还会产生哪些其他影响?

有一个依赖于此运算符的代码库,不存在其他成员,只有提到的引用。是否需要以某种方式使代码库适应此更改,或者它是安全的?

C++ 复制和交换 pass-by-const-reference

评论

1赞 Holt 11/4/2019
您通常(从不?)想要一个 by-value 重载和一个 rvalue-reference 重载,因为这些在大多数时候(总是?)都是模棱两可的。例如,参见 stackoverflow.com/questions/28701039/...
2赞 Holt 11/4/2019
顺便说一句,这根本不是复制和交换的成语......在这里,你基本上将以无限递归的方式运行。您不能在 swap 和 in swap 中使用。您希望您的交换实际执行交换。在您的例子中,swap 的主体应该是 .swapoperator=operator=std::swap(first.my_reference, second.my_reference)
0赞 molbdnilo 11/4/2019
类定义中的成员函数定义是隐式的。这不是一个错误,但混乱会降低可读性。inline
1赞 molbdnilo 11/4/2019
成员的交换和复制以及类型与它无关。这与(即同样“良好”的重载)的情况相同。void f(int); void f(int&&); int main{ f(1);}

答:

4赞 sebrockm 11/4/2019 #1

如果你仔细遵循你链接的描述,你会看到你必须只有一个重载,并且这个重载需要按值获取它的参数。因此,只需删除重载即可使您的代码可编译。operator=operator=(I_hold_reference&&)

然而,这并不是唯一的问题。你不交换!相反,它会分配 to 的副本,并保持不变。swapsecondfirstsecond

这是你想要的:

class I_hold_reference
{
    I_hold_reference(const int& number_reference)
     : my_reference(number_reference){}

    friend void swap(I_hold_reference& first, I_hold_reference& second)
    {
        using std::swap;
        swap(first.my_reference, second.my_reference);
    }

    I_hold_reference& operator=(I_hold_reference other)
    {
        swap(*this, other);
        return *this;
    }
private:
    reference_wrapper<const int> my_reference;
};

注意:我删除了不必要的 s,因为成员函数是隐式内联的。此外,我在你的类中声明了该函数。您可以在共享的链接中找到对此的解释。inlineswap

此外,在这个特定示例中,首先不需要使用复制和交换习语。 不是手动维护的资源,这意味着它内置了适当的复制和移动语义。因此,在这个特定示例中,编译器生成的复制和移动运算符将具有与此处手动创建的运算符完全相同的行为。所以,你应该使用它们,而不是以任何方式写你自己的。另一方面,如果这只是一个玩具示例,并且真实类中确实有更多资源需要手动管理,那么这就是要走的路。std::reference_wrapper

评论

0赞 Dávid Tóth 11/4/2019
哦,我犯了错误,谢谢你指出来!在实际代码中,我没有使用 reference-wrapper,而是使用 const 引用。 实际上与此不兼容,但分配在那里有效。此外,在代码中,我没有使用 int 引用,而是使用实际对象(带有移动赋值构造函数)。你会说代码对这些添加是有效的吗?std::swap
0赞 sebrockm 11/4/2019
@DavidTóth 引用只能初始化,之后不能重新分配。这就是为什么它不起作用的原因。此外,任何赋值运算符都不适用于具有普通引用作为成员的类。绕过这个限制恰恰是有好处的,所以使用它的想法还不错。但无论哪种方式,如果我正确理解了您的场景,您就不需要复制和交换的习语。例如,仅当您手动分配内存(使用 和 )或打开文件或数据库连接时才需要它。swapreference_wrappernewdelete
0赞 Dávid Tóth 11/4/2019
好的,感谢您的详细解答和帮助!
2赞 darune 11/4/2019 #2

使用转发引用将绑定到任何右值(临时/转发引用)。这些类别也可以作为值传递,然后不明确。也就是说,例如,临时值是模棱两可的,因为左值不会是(使用值重载)。&&

其中,非常量引用永远不能绑定到临时引用或转发引用常量引用可以,但不是模棱两可的。

评论

0赞 Holt 11/4/2019
“如果更改为常量引用 [...]”- 你应该澄清这部分。如果将 by-value 重载替换为 const-reference 重载,则不会有歧义,这基本上是我们在重载 和 时总是这样做的。如果将 rvalue-overload 替换为 const-reference 重载,则存在歧义,但 OP 提到替换 by-value 重载,而不是 rvalue 重载。operator=(A const&)operator=(A&&)
0赞 darune 11/4/2019
@Holt非常感谢 - 我想我需要阅读有关这些重载的规则。
0赞 Dávid Tóth 2/18/2021
感谢您的澄清!这解释了模棱两可的原因!