【问题标题】:Why isn't my assignment operator getting called?为什么我的赋值运算符没有被调用?
【发布时间】:2011-11-14 21:05:03
【问题描述】:

我很困惑...为什么我的赋值运算符没有在这里被调用?

template<typename This>
struct mybase
{
    This& operator =(const This &other)
    {
        __debugbreak();  // The debugger should break here, but doesn't.
        return static_cast<This &>(*this):
    }
};

struct myderived : mybase<myderived>
{
    int x;
};

int main()
{
    myderived a = myderived();  // And yes, I know it's redundant...
    myderived b = myderived();
    a = b;

    return 0;
}

【问题讨论】:

    标签: c++ operator-overloading assignment-operator


    【解决方案1】:

    mybase::operator= 被自动生成的复制赋值运算符myderived::operator=隐藏

    您可以使用using 声明使基类运算符在派生类中可见。

    编辑:为每个请求添加示例:

    template<typename This>
    struct mybase
    {
        This& operator =(const This &other)
        {
            //__debugbreak();  // The debugger should break here, but doesn't.
            return static_cast<This &>(*this);
        }
    };
    
    struct myderived : mybase<myderived>
    {
        using mybase<myderived>::operator=;
        int x;
    };
    
    int main()
    {
        myderived a = myderived();  // And yes, I know it's redundant...
        myderived b = myderived();
        a = b;
    }
    

    这与 Visual C++ 10.0 和 Comeau Online 一起编译得很好。后者实际上意味着它是良好的标准 C++。但是,该代码无法使用 MinGW g++ 4.4.1 编译(编译器错误)。

    编辑 2: 实际上,现在检查一下,使用 Visual C++ 10.0 可以编译,但不会调用基类运算符。所以也许 g++ 是正确的。 using 通常是引入基类赋值运算符(或其他)的方式,但在这种情况下,它与派生类的复制赋值运算符具有相同的签名,我还不知道 Visual C++ 的行为是否正确与否——这是语言的一个极端情况。

    编辑 3:我检查了 N3290(与 C++11 相同的标准草案),它说

    §12.8/18:
    如果类定义没有显式声明复制赋值运算符,则隐式声明一个。如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的复制赋值运算符被定义为删除;否则,它被定义为默认(8.4)。

    我个人将其解释为,使用 using 声明,类“声明”了一个复制赋值运算符,因此不应隐式生成一个(就像 Visual C++ 10.0 一样)。但是,这是该语言的一个极端情况。其他人可能对此有不同的解释,如上所述,编译器不同!

    干杯,

    【讨论】:

    • 真的吗?那将是真棒;你能提供一个如何使用using的例子吗?
    • cygwin 上的 clang++ 3.0 生成以下内容:error: use of overloaded operator '=' is ambiguous (with operand types 'myderived' and 'myderived')。所以我们基本上得到 2 个编译错误,1 个基类和 1 个派生类调用。即,在那种情况下,也许对标准有一种正确的解释,但似乎没有人同意它是什么;)
    【解决方案2】:

    这一行:

    a = b;  
    

    显然要求myderived 重载了复制赋值运算符。它可以由编译器隐式生成,也可以由myderived 类显式定义:

    12.8 复制类对象 [class.copy]

    9. 用户声明的复制赋值运算符 X::operator= 是 类 X 的非静态非模板成员函数,恰好为 1 XX&amp;const X&amp;volatile X&amp;const volatile X& 类型的参数。

    您尝试在 mybase 类中创建用户声明的复制赋值运算符,但根据 C++ 标准,它实际上不是复制赋值运算符。想象一下,如果我们用myderivedThis 进行类型替换:

    // Hypothetical class generated by the compiler from
    // the mybase template class with This = myderived
    struct mybase_myderived 
    {
        myderived& operator =(const myderived &other) 
        { 
            // ...
        } 
    };
    

    显然这不是一个复制赋值运算符,因为参数other 的类型是const myderived&amp;,而不是const mybase&amp;。如果other 参数的类型为const mybase&amp;mybasemybase&amp;,那么它将是一个有效的复制赋值运算符,可以由myderived 中的默认复制赋值运算符调用。但在这种情况下并非如此,因此编译器仍然会为mybase 生成一个默认的复制赋值运算符,这在这种情况下当然什么也不做。

    myderived 中编译器生成的默认复制赋值运算符调用mybase 中编译器生成的默认复制赋值运算符。那么最终会发生什么,因此,operator=(const myderived &amp;other) 重载永远不会被调用。

    编译器不直接调用mybase::operator= 的原因是因为它被myderived 中的编译器生成的复制赋值运算符隐藏了,正如Alf P. Steinbach 在this answer 中指出的那样。

    【讨论】:

    • 嗯?什么是“复制赋值运算符”?
    • 我不明白这是如何不是复制分配运算符...根据您的链接,它必须是:复制分配运算符[。 ..] 是赋值运算符的一种特殊情况,其中源(右侧)和目标(左侧)属于同一类类型。
    • @Mehrdad:RHS 是This。 LHS 是mybase&lt;This&gt;
    • @Mehrdad:C++ 标准 12.8/9 说:“用户声明的复制赋值运算符 X::operator= 是类 X 的非静态非模板成员函数,只有一个类型的参数XX&amp;const X&amp;volatile X&amp;const volatile X&amp;"。所以它不是复制赋值运算符,除非X 与它所属的类的类型相同。
    • @Tomalak:好的,谢谢。 :) 出于某种原因,我希望它的行为类似于某种 mixin,但显然它并没有做到这一点。
    【解决方案3】:

    因为编译器在 myderived.xml 中引入了默认的赋值运算符。覆盖它并自己调用您的基本赋值运算符。或者也许一个 using 指令会有所帮助?尝试在 myderived 正文中使用 mybase::operator=。

    【讨论】:

      【解决方案4】:

      因为derived中默认的赋值运算符会隐藏base中的重载。

      【讨论】:

      • 真的等一下??有没有办法避免这种情况,同时将赋值运算符保留在基类中?
      • @Mehrdad: using base::operator=
      • @Tony:但这意味着我仍然必须在每个课程中手动覆盖它......这是我试图避免的。有没有办法避免这种情况?
      猜你喜欢
      • 2015-04-13
      • 2013-04-29
      • 1970-01-01
      • 2019-03-19
      • 2015-12-14
      • 2016-07-25
      • 2013-09-22
      • 2011-01-21
      • 2020-11-22
      相关资源
      最近更新 更多