C++ C+;中运算符=的继承问题+;

C++ C+;中运算符=的继承问题+;,c++,inheritance,abstract-class,operator-keyword,C++,Inheritance,Abstract Class,Operator Keyword,我在运算符=”的继承方面遇到问题。为什么这段代码不起作用,最好的解决方法是什么 #include <iostream> class A { public: A & operator=(const A & a) { x = a.x; return *this; } bool operator==(const A & a) { return x == a.x; }

我在运算符=”的继承方面遇到问题。为什么这段代码不起作用,最好的解决方法是什么

#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;
}
#包括
甲级
{
公众:
A&A运算符=(常量A&A)
{
x=a.x;
归还*这个;
}
布尔运算符==(常数A和A)
{
返回x==a.x;
}
虚拟int get()=0;//抽象
受保护的:
int x;
};
B类:公共A
{
公众:
B(int x)
{
这个->x=x;
}
int get()
{
返回x;
}
};
C类:公共A类
{
公众:
C(整数x)
{
这个->x=x;
}
int get()
{
返回x;
}
};
int main()
{
B(3);
C(7);
printf(“B:%dc:%db==C:%d\n”,B.get(),C.get(),B==C);
b=c;//编译错误
//错误:“b=c”中的“运算符”不匹配
//注:候选项为B&B::operator=(常量B&)
printf(“B:%dc:%db==C:%d\n”,B.get(),C.get(),B==C);
返回0;
}

发生的事情是,编译器为没有运算符的任何类生成的默认
运算符=
正在隐藏基类“
运算符=
。在这种特殊情况下,编译器将在后台为您生成
const B&B::operator=(const B&)
。您的赋值与此运算符匹配,并完全忽略您在
类别A
中声明的赋值。由于a
C&
无法转换为a
B&
编译器会生成您看到的错误

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

从ISA的角度考虑。是否应该允许将
汽车
分配给
仅仅因为它们都是
车辆


为了使这样的工作,你应该使用的模式。信封(aka handle)是一个专门的类,它的唯一任务是保存从特定基类(字母)派生的某个类的实例。句柄将除赋值之外的所有操作转发给包含的对象。对于赋值,它只是将内部对象的实例替换为构造的副本(使用“克隆”方法(也称为虚拟构造函数))已分配自对象的副本。

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


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

通常,运算符=在B中定义为

B& operator=(B const &);
由于B不是“C”的明确且可访问的基,编译器不允许从C转换为B

如果您确实希望将“C”分配给“B”,则“B”应支持适当的分配运算符

B& operator=(C const &);

如果不在类中声明复制赋值运算符,编译器将隐式地为您声明一个。隐式声明的复制赋值运算符将隐藏任何继承的赋值运算符(请阅读C++中的“名称隐藏”),这意味着任何继承的赋值运算符将对非限定名称查找过程“不可见”(这是在执行
b=C
时发生的情况),除非您采取特定步骤“取消隐藏”它们

在您的例子中,类
B
没有显式声明的复制赋值运算符。这意味着编译器将声明

B& B::operator =(const B&)
含蓄地。它将隐藏从
A
继承的运算符。线路

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

如果希望编译代码,可以使用using声明通过添加

using A::operator =;
B
的定义。代码现在可以编译了,尽管它不是一种好的样式。您必须记住,在这种情况下,
b=c
赋值将调用
A::operator=
,它只赋值所涉及对象的
A
部分。(但这显然是你的意图。)

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

b.A::operator =(c);
(可能不是解决方案&可能不是你应该做的)但是。。。如果你真的必须这样做,有一种方法可以强制解决这个问题:

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

想要将一个基子对象从一个派生类复制到另一个派生类,这一点都不是不合理的。考虑从报价中创建订单。它们的类型不同,但它们共享关于客户(名称/地址/等)和订购产品的全部信息,但两者都不是另一个的真正超集(订单没有报价过期日期,报价没有账单信息)。当然,这应该是一个普通函数,而不是
操作符=
,这样就不会有任何编译器生成的版本对派生类隐藏它。当你说它将隐藏它时,我非常确定默认赋值操作符将使用用户重载来赋值“a”部分。但是,它不允许您分配从A派生的任何内容,这就是OP的代码无法编译的原因。@CashCow:是的,您是对的。正确的方法是继承的
运算符=
的名称对于非限定名称查找不可见。当然,编译器仍然知道abo