“切片”是指将派生类的对象分配给基类的实例,从而丢失部分信息--其中一些信息被“切片”掉了。
例如,
class A {
int foo;
};
class B : public A {
int bar;
};
因此b
类型的对象有两个数据成员,foo
和bar
。
如果你要这样写:
B b;
A a = b;
则b
中关于成员bar
的信息在a
中丢失。
这里的大多数答案都无法解释切片的实际问题是什么。他们只解释切片的良性案例,而不解释奸诈的案例。与其他答案一样,假设您处理的是两个类A
和B
,其中B
(公开)来源于A
。
在这种情况下,C++允许将b
的实例传递给a
的赋值运算符(以及复制构造函数)。这是因为b
的实例可以转换为常量a&
,这是赋值运算符和复制构造函数希望它们的参数是什么。
B b;
A a = b;
那里没有什么不好的事情发生--您要求a
的一个实例,它是B
的副本,而这正是您得到的。当然,A
不会包含B
的某些成员,但它应该如何包含?这是一个a
,毕竟不是B
,所以它甚至没有听说过这些成员,更不用说能够存储它们了。
B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!
您可能认为b2
将是b1
的副本。但是,唉,不是!如果您检查它,就会发现b2
是弗兰肯斯坦式的生物,由b1
的一些块(b
从a
继承的块)和b2
的一些块(只有b
包含的块)组成。哎哟!
怎么了?当然,C++默认情况下不会将赋值运算符视为virtual
。因此,行a_ref=b1
将调用a
的赋值运算符,而不是b
的赋值运算符。这是因为,对于非虚函数,声明的(形式上:静态)类型(即a&
)决定调用哪个函数,而实际的(形式上:动态)类型(即b
,因为a_ref
引用了b
的实例)则相反。现在,A
的赋值运算符显然只知道A
中声明的成员,因此它将只复制那些成员,而不改变B
中添加的成员。
只给对象的部分赋值通常没有什么意义,但不幸的是,C++没有提供禁止这样做的内置方法。不过,你可以自己滚。第一步是使赋值运算符虚拟化。这将保证调用的始终是实际类型的赋值运算符,而不是声明类型的赋值运算符。第二步是使用dynamic_cast
验证分配的对象是否具有兼容的类型。第三步是在一个(protected!)中进行实际赋值成员assign()
,因为b
的assign()
可能希望使用a
的assign()
复制a
的成员。
class A {
public:
virtual A& operator= (const A& a) {
assign(a);
return *this;
}
protected:
void assign(const A& a) {
// copy members of A from a to this
}
};
class B : public A {
public:
virtual B& operator= (const A& a) {
if (const B* b = dynamic_cast<const B*>(&a))
assign(*b);
else
throw bad_assignment();
return *this;
}
protected:
void assign(const B& b) {
A::assign(b); // Let A's assign() copy members of A from b to this
// copy members of B from b to this
}
};
注意,为了方便起见,b
的operator=
协变重写了返回类型,因为它知道返回的是b
的实例。