为什么赋值给基类是有效的,但是赋值给派生类的编译错误?
这是一个面试问题。 考虑以下:
struct A {}; struct B : A {}; A a; B b; a = b; b = a;
为什么b = a;
抛出一个错误,而a = b;
是完美的吗?
因为B
的隐式声明的复制赋值运算符隐藏了B
的隐式声明的复制赋值运算符。
所以对于b = a
,只有B
的operator=
是候选人。 但是它的参数有B const&
型B const&
,不能用A
参数初始化(你需要向下)。 所以你得到一个错误。
因为每个B是一个A,但不是每个A都是一个B.
编辑下面的评论,使事情更清楚一点(我修改你的例子):
struct A {int someInt;}; struct B : A {int anotherInt}; A a; B b; /* Compiler thinks: B inherits from A, so I'm going to create a new A from b, stripping B-specific fields. Then, I assign it to a. Let's do this! */ a = b; /* Compiler thinks: I'm missing some information here! If I create a new B from a, what do I put in b.anotherInt? Let's not do this! */ b = a;
在你的例子中, someInt
和anotherInt
没有属性,所以它可以工作。 但编译器不会允许它。
确实, B
是A
,但A
不是B
,但是这个事实只有在你使用指针或者引用A
和B
的时候才是直接适用的。 这里的问题是你的赋值操作符。
struct A {}; struct B : A {};
相当于
struct A { A& operator=(const A&); }; struct B : A { B& operator=(const B&); };
所以当你在下面分配时:
A a; B b; a = b;
a上的赋值运算符可以用b
的参数来调用,因为B
是A
,所以b
可以作为A&
被传递给赋值运算符。 请注意,赋值操作符只知道A
的数据,而不知道B
,所以B中不属于A的任何成员都会丢失 – 这就是所谓的“分片”。
但是当你试图分配:
b = a;
a
是typesA
,它不是B
,所以a
不能和B&
的赋值运算符相匹配。
你会认为b=a
应该调用inheritance的A& A::operator=(const A&)
,但事实并非如此。 赋值运算符B& B::operator=(const B&)
隐藏了将从A
inheritance的运算符。 它可以using A::operator=;
来重新恢复using A::operator=;
宣言。
我已经改变了你的结构的名字,使原因很明显:
struct Animal {}; struct Bear : Animal {}; Animal a; Bear b; a = b; // line 1 b = a; // line 2
显然,任何熊也是一种动物,但不是每一只动物都可以被认为是一只熊。
因为每个B“isa”A,B的任何实例也必须是A的一个实例:根据定义,它具有与A的任何其他实例相同顺序的相同成员。将b复制到a中会丢失特定于B的成员,但完全填充满足A的要求的结构的成员。另一方面,将a复制到b可能会使b不完整,因为B可能具有比A更多的成员。在这里很难看到,因为A和B都不是有任何成员,但这就是为什么编译器允许一个分配,而不是其他。
请记住,如果没有显式声明的复制赋值操作符,将隐式声明和定义任何类(并且结构是C ++中的类)。
对于struct A
它将具有以下签名:
A& A::operator=(const A&)
它只是执行其子对象的成员分配。
a = b;
是可以的,因为B
将与A::operator=(const A&)
的const A&
参数相匹配。 由于只有A
的成员被“成员分配”给目标,所以B
中不属于A
任何成员都会丢失 – 这就是所谓的“切片”。
对于struct B
,implcit赋值运算符将具有以下签名:
B& B::operator=(const B&)
b = a;
不正确,因为A
不会匹配const B&
参数。
如果我接受采访的话,我会用一点哲学的方式来解释。
a = b;
是有效的,因为每个B
包含A
作为其一部分。 所以可以从B
提取A
但是, A
不包含B
因此b
在A
内找不到B
; 这就是为什么,
b = a;
是无效的。
[类似地, void*
可以在任何Type*
find,但Type*
不能在void*
find(因此我们需要一个cast)。]