【问题标题】:Trouble with inheritance of operator= in C++C++中运算符=的继承问题
【发布时间】:2011-04-22 09:12:36
【问题描述】:

我在继承 operator= 时遇到问题。为什么这段代码不起作用,修复它的最佳方法是什么?

#include <iostream>

class A
{
public:
    A & operator=(const A & a)
    {
        x = a.x;
        return *this;
    }

    bool operator==(const A & a)
    {
        return x == a.x;
    }

    virtual int get() = 0; // Abstract

protected:
    int x;
};

class B : public A
{
public:
    B(int x)
    {
        this->x = x;
    }

    int get()
    {
        return x;
    }
};

class C : public A
{
public:
    C(int x)
    {
        this->x = x;
    }

    int get()
    {
        return x;
    }
};

int main()
{
    B b(3);
    C c(7);
    printf("B: %d C: %d B==C: %d\n", b.get(), c.get(), b==c);

    b = c; // compile error
    // error: no match for 'operator= in 'b = c'
    // note: candidates are B& B::operator=(const B&)

    printf("B: %d C: %d B==C: %d\n", b.get(), c.get(), b==c);
    return 0;
}

【问题讨论】:

    标签: c++ inheritance abstract-class operator-keyword


    【解决方案1】:

    如果你没有在一个类中声明复制赋值操作符,编译器会为你隐式声明一个。隐式声明的复制赋值运算符将隐藏任何继承的赋值运算符(阅读 C++ 中的“名称隐藏”),这意味着任何继承的赋值运算符都将变为“不可见”不合格 em> 名称查找过程(当您执行 b = c 时会发生这种情况),除非您采取特定步骤“取消隐藏”它们。

    在您的情况下,B 类没有明确声明的复制赋值运算符。这意味着编译器将声明

    B& B::operator =(const B&)
    

    隐含的。它将隐藏从A 继承的运算符。线

    b = c;
    

    不编译,因为这里唯一的候选者是上面隐式声明的B::operator =(编译器已经告诉过你了);所有其他候选人都被隐藏。并且由于c 不能转换为B&amp;,因此上述赋值无法编译。

    如果你想让你的代码编译,你可以使用 using-declaration 通过添加来取消隐藏继承的A::operator =

    using A::operator =;
    

    到类B的定义。代码现在将编译,虽然它不是一个好的样式。您必须记住,在这种情况下,b = c 分配将调用A::operator =,它仅分配所涉及对象的A 部分。 (但显然这是你的意图。)

    或者,在这种情况下,您始终可以使用名称的限定版本来解决名称隐藏问题

    b.A::operator =(c);
    

    【讨论】:

    • 当你说它会隐藏它时,我很确定默认赋值运算符将使用用户重载来分配“A”部分。但是,它不允许您分配从 A 派生的任何内容,这就是 OP 的代码无法编译的原因。
    • @CashCow:是的,你是对的。正确的说法是继承的operator = 的名称对于unqualified name lookup 变得不可见。当然,编译器仍然知道该运算符,并且仍然在其他上下文中使用它。
    【解决方案2】:

    发生的情况是编译器为任何没有的类生成的默认operator = 隐藏了基类'operator =。在这种特殊情况下,编译器在幕后为您生成const B &amp;B::operator =(const B &amp;)。您的分配与此运算符匹配,并完全忽略您在class A 中声明的那个。由于无法将 C&amp; 转换为 B&amp;,因此编译器会生成您看到的错误。

    您希望这种情况发生,即使现在看起来很烦人。它会阻止您编写的代码工作。您不希望这样的代码工作,因为它允许将不相关的类型(B 和 C 具有共同的祖先,但继承中唯一重要的关系是父->子->孙关系,而不是兄弟关系)分配给一个另一个。

    从 ISA 的角度考虑它。是否应该允许将 Car 分配给 Boat,因为它们都是 Vehicles

    为了完成类似的工作,您应该使用Envelope/Letter 模式。信封(又名句柄)是一个专门的类,它的唯一工作是保存从特定基类(字母)派生的某个类的实例。句柄转发所有操作,但分配给包含的对象。对于赋值,它只是将内部对象的实例替换为从对象赋值的复制构造(使用“克隆”方法(又名虚拟构造函数))副本。

    【讨论】:

    • 想要将基子对象从一个派生类复制到另一个派生类,这完全不是不合理的。考虑从报价创建订单。它们不是同一类型,但它们共享有关客户(名称/地址/等)和订购产品的一大堆信息,但两者都不是另一个的真正超集(订单没有报价到期日期,报价没有账单信息)。当然,这个函数应该是一个普通函数,而不是operator=,这样就不会有任何编译器生成的版本将它隐藏在派生类中。
    【解决方案3】:

    您不能像这样跨层次分配 - B 和 C 是 A 的不同子类。您可以将 B 分配给 B 或将 C 分配给 C,但不能将 C 分配给 B,反之亦然。

    您可能希望在 B 和 C 中实现 operator=,在尝试之前将分配的 A 部分委托给 A::operator=。否则,这些课程的 B 和 C 特定部分将在作业中丢失。

    【讨论】:

      【解决方案4】:

      通常,operator= 在 B 中定义为

      B& operator=(B const &);
      

      由于 B 不是“C”的明确且可访问的基数,因此编译器不允许从 C 转换为 B。

      如果你真的想将'C'分配给'B','B'应该支持适当的赋值运算符作为

      B& operator=(C const &);
      

      【讨论】:

        【解决方案5】:

        (可能不是解决方案,也可能不是你应该做的)但是......如果你真的必须的话,有一种方法可以强制解决问题:

         (A&)(*(&b)) = (A&)(*(&c))
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-08-12
          • 2015-11-19
          • 2020-05-29
          • 2015-02-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-12-05
          相关资源
          最近更新 更多