这是一个面试题。考虑以下内容:
struct A {};
struct B : A {};
A a;
B b;
a = b;
b = a;
为什么执行 b = a;
会抛出错误,而执行 a = b;
则完全没有问题?这是一个面试题。考虑以下内容:
struct A {};
struct B : A {};
A a;
B b;
a = b;
b = a;
为什么执行 b = a;
会抛出错误,而执行 a = b;
则完全没有问题?因为 B
的隐式声明拷贝赋值运算符覆盖了 A
的隐式声明拷贝赋值运算符。
所以对于语句 b = a
,只有 B
的 operator=
能够被选中作为候选。但它的参数类型是 B const&
,不能通过一个 A
类型的参数进行初始化(需要向下转型)。因此会出现错误。
B
的定义更改为struct B : A { using A::operator=; };
,观察两行代码都会编译通过。 - Johannes Schaub - litbB
中添加 using A::operator=;
将会修复它,如果这有助于澄清。 - Michael MrozekA a;
B b;
`
a = b; b = a; // 这里没有编译错误 - badawi因为每个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
这些属性,所以它可能会工作。但是编译器无论如何都不会允许它。A::operator=
和 B::operator=
的一些技术细节缺失,其他回答更好地涵盖了这些内容。 - Ken Bloom确实,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
的赋值运算符只知道A
中的数据,而不知道B
中的数据,因此B
中不属于A
的任何成员都会丢失——这就是所谓的“切片”。
但是当你尝试进行赋值时:
b = a;
a
的类型是A
,不是B
,所以a
无法匹配b
的赋值运算符的B&
参数。你可能认为b=a
只会调用继承的A& A::operator=(const A&)
,但事实并非如此。赋值运算符B& B::operator=(const B&)
会隐藏从A
继承的运算符。可以使用using A::operator=;
声明来恢复其作用。struct Animal {};
struct Bear : Animal {};
Animal a;
Bear b;
a = b; // line 1
b = a; // line 2
显然,任何熊都是动物,但并非所有动物都可以被视为熊。
因为每个B“是一个”A,所以B的每个实例也必须是A的实例:根据定义,它具有与A的任何其他实例相同的成员和相同的顺序。将b复制到a中会丢失B特定的成员,但完全填充a的成员,从而得到一个满足A要求的结构体。另一方面,将a复制到b可能会使b不完整,因为B可能比A具有更多的成员。这很难在这里看到,因为A和B都没有任何成员,但这就是编译器允许一种赋值而不允许另一种赋值的原因。
Animal::setCovering(Covering c);
,你可以这样做 Bear b; b.setCovering(Feathers);
。因此,你也可以认为 Animal 是 Bear,或者两者都不是彼此的子类。重要的不是谁继承自谁,而是 什么 被继承了。 - R. Martinho Fernandes请记住,如果没有显式声明复制赋值运算符,那么将为任何类(结构体在C++中也属于类)隐式地声明和定义一个复制赋值运算符。
对于struct A
,它的签名如下:
A& A::operator=(const A&)
a = b;
是可以的,因为 B
会与 A::operator=(const A&)
的 const A&
参数匹配。由于仅将 A
的成员“逐成员赋值”到目标,因此不属于 A
的任何 B
成员都会丢失 - 这被称为“切片”。struct B
,隐式赋值运算符将具有以下签名:B& B::operator=(const B&)
b = a;
不可以,因为A
与const B&
参数不匹配。
如果我正在接受面试,那么我会用一些哲学的方式来解释。
a = b;
是有效的,因为每个B
都包含A
作为其一部分。 因此,a
可以从B
内提取A
。 但是,A
不包含B
。 因此,b
无法从A
内找到B
;这就是为什么,
b = a;
无效。
[类比地说,void*
可以在任何 Type*
中找到,但 Type*
不能在 void*
中找到(因此我们需要一个强制转换)。]
void*
不是有损的。 - Konrad Rudolphdouble
到 int
的(允许的)隐式转换。C++ 允许这样做的事实是荒谬的。大多数现代语言都禁止这样做,因为它很危险。 - Konrad Rudolpha = b;
时,损失是可以预料的。这就是为什么它被称为切片。类比只是为了支持自然转换。 - iammilind
struct
就是class
。这里没有任何“本质”被混淆。 - Billy ONeal