【问题标题】:error: ambiguous overload for 'operator=' in swap function using the copy-and-swap idiom错误:使用复制和交换习语的交换函数中“operator=”的重载不明确
【发布时间】:2020-02-29 13:44:22
【问题描述】:

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

示例代码:

#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')
}

当复制赋值运算符被更改为通过引用而不是按值获取其参数时,该错误已得到修复。

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

为什么这可以修复错误? 一种可能的暗示是链接问题中引用的Important optimization possibility 丢失了。参考文献是这样吗? 这种变化的其他影响是什么?

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

【问题讨论】:

  • 您通常不(从不?)想要一个按值重载和一个带有右值引用的重载,因为这些在大多数情况下都是模棱两可的(总是?)。参见,例如,stackoverflow.com/questions/28701039/…
  • 顺便说一句,这根本不是复制和交换的习惯用法……你基本上会在这里无限递归地运行。您不能在 operator= 中使用 swap 和在交换中使用 operator=。您希望您的交换实际执行交换。在你的情况下,交换的主体应该是std::swap(first.my_reference, second.my_reference)
  • 类定义中的成员函数定义隐含inline。不是错误,但混乱会降低可读性。
  • 成员的交换复制与类型无关。这与void f(int); void f(int&amp;&amp;); int main{ f(1);} 的情况相同(即同样“好”的重载)。

标签: c++ copy-and-swap pass-by-const-reference


【解决方案1】:

如果您仔细按照链接的描述进行操作,您会发现您必须只有一个 operator= 重载,并且需要按值获取其参数。因此,简单地删除 operator=(I_hold_reference&amp;&amp;) 重载将使您的代码可编译。

但是,这不是唯一的问题。您的 swap 不会交换!相反,它会将second 的副本分配给first,并保持second 不变。

这就是你想要的:

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;
};

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

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

【讨论】:

  • 哦,我犯了错误,感谢您指出这一点!在实际代码中,我没有使用引用包装器,而是使用 const 引用。 std::swap 实际上与它不兼容,但该分配在那里有效。此外,在代码中,我没有使用 int 引用,而是使用实际对象(带有移动赋值构造函数)。您认为添加这些代码后代码是否有效?
  • @DavidTóth 引用只能被初始化,之后不能被重新分配。这就是为什么swap 不能使用它的原因。此外,任何赋值运算符都不适用于具有普通引用作为成员的类。绕过这个限制正是reference_wrapper 的优点,所以使用它的想法还不错。但无论哪种方式,如果我正确理解您的情况,您就不需要复制和交换成语。仅当您手动分配内存(使用newdelete)或打开文件或数据库连接时才需要它。
  • 好的,谢谢您的详细解答和帮助!
【解决方案2】:

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

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

【讨论】:

  • "如果您更改为 const 引用 [...]" - 您应该澄清这部分。如果将按值重载替换为 const-reference 重载,则没有歧义,这基本上是我们在重载 operator=(A const&amp;)operator=(A&amp;&amp;) 时总是做的事情。如果您用 const-reference 重载替换右值重载,则存在歧义,但 OP 提到替换按值重载,而不是右值重载。
  • @Holt 非常感谢 - 我想我需要阅读有关这些重载的规则。
  • 感谢您的澄清!这就解释了歧义!
猜你喜欢
  • 2011-10-28
  • 2013-08-10
  • 2014-06-23
  • 2015-08-04
  • 1970-01-01
  • 2016-02-17
  • 2023-03-23
  • 1970-01-01
  • 2020-05-27
相关资源
最近更新 更多