在 “切片” 中,您将派生类的对象分配给基类的实例,从而丢失了部分信息 - 其中一些信息被 “切片” 了。
例如,
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
的实例可以转换为const 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
的副本。但是,a, 不是 !如果检查它,您会发现b2
是弗兰肯斯坦主义的生物,它由b1
一些块( B
从A
继承的块)和b2
一些块(仅B
包含的块)组成。哎哟!
发生了什么?好吧,默认情况下,C ++ 不会将赋值运算符视为virtual
。因此,行a_ref = b1
将调用A
的赋值运算符,而不是B
的赋值运算符。这是因为,对于非虚函数, 声明的类型(即A&
)确定要调用哪个函数,而不是实际的类型(应为B
,因为a_ref
引用B
的实例)。现在, A
的赋值运算符显然只知道A
声明的成员,因此它将仅复制那些成员,而保持B
添加的成员不变。
仅分配给对象的一部分通常没有什么意义,但是不幸的是,C ++ 没有提供内置的方法来禁止这种情况。但是,您可以自己滚动。第一步是使赋值运算符为virtual 。这样可以确保始终调用的是实际类型的赋值运算符,而不是声明的类型。第二步是使用dynamic_cast
验证分配的对象是否具有兼容类型。第三步是在(受保护的!)成员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
的实例。
如果您具有基类A
和派生类B
,则可以执行以下操作。
void wantAnA(A myA)
{
// work with myA
}
B derived;
// work with the object "derived"
wantAnA(derived);
现在,方法wantAnA
需要derived
的副本。但是, derived
的对象无法完全复制,因为类B
可以发明不在其基类A
其他成员变量。
因此,要调用wantAnA
,编译器将 “分片” 派生类的所有其他成员。结果可能是您不想创建的对象,因为
A
(类B
所有特殊行为都丢失了)。