在c++中,什么是对象切片,什么时候发生?
当前回答
我看到所有的答案都提到了当数据成员被切片时对象切片发生的情况。这里我举了一个示例,说明这些方法不会被重写:
class A{
public:
virtual void Say(){
std::cout<<"I am A"<<std::endl;
}
};
class B: public A{
public:
void Say() override{
std::cout<<"I am B"<<std::endl;
}
};
int main(){
B b;
A a1;
A a2=b;
b.Say(); // I am B
a1.Say(); // I am A
a2.Say(); // I am A why???
}
B(对象B)从A(对象a1和a2)导出。正如我们所期望的,b和a1调用它们的成员函数。但从多态性的角度来看,我们不期望由b赋值的a2不会被重写。基本上,a2只保存b的A类部分,即C++中的对象切片。
要解决此问题,应使用引用或指针
A& a2=b;
a2.Say(); // I am B
or
A* a2 = &b;
a2->Say(); // I am B
其他回答
所以为什么丢失派生信息很糟糕。。。因为派生类的作者可能已经改变了表示方式,使得分割额外信息改变了对象所表示的值。如果派生类用于缓存对某些操作更有效但转换回基表示代价高昂的表示,则会发生这种情况。
还认为有人应该提到你应该做什么来避免切片。。。获取C++编码标准、101条规则指南和最佳实践的副本。处理切片是#54。
它提出了一种有点复杂的模式来完全解决这个问题:有一个受保护的复制构造函数、一个受受保护的纯虚拟DoClone,以及一个带有断言的公共克隆,如果(进一步)派生类未能正确实现DoClone,该断言将告诉您。(克隆方法对多态对象进行适当的深度复制。)
您还可以在基显式上标记复制构造函数,如果需要,它允许显式切片。
C++中的切片问题源于其对象的值语义,这主要是由于与C结构的兼容性。您需要使用显式引用或指针语法来实现大多数其他语言中的“正常”对象行为,即对象总是通过引用传递。
简单的答案是,通过按值将派生对象分配给基础对象来切片对象,即,剩余对象只是派生对象的一部分。为了保持值语义,切片是一种合理的行为,其用途相对较少,这在大多数其他语言中都不存在。有些人认为它是C++的一个特性,而许多人则认为这是C++的怪癖/错误特性之一。
当派生类对象分配给基类对象时,派生类对象的所有成员都将复制到基类对象,但基类中不存在的成员除外。这些成员被编译器切片。这称为对象切片。
下面是一个示例:
#include<bits/stdc++.h>
using namespace std;
class Base
{
public:
int a;
int b;
int c;
Base()
{
a=10;
b=20;
c=30;
}
};
class Derived : public Base
{
public:
int d;
int e;
Derived()
{
d=40;
e=50;
}
};
int main()
{
Derived d;
cout<<d.a<<"\n";
cout<<d.b<<"\n";
cout<<d.c<<"\n";
cout<<d.d<<"\n";
cout<<d.e<<"\n";
Base b = d;
cout<<b.a<<"\n";
cout<<b.b<<"\n";
cout<<b.c<<"\n";
cout<<b.d<<"\n";
cout<<b.e<<"\n";
return 0;
}
它将产生:
[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'
切片问题很严重,因为它会导致内存损坏,而且很难保证程序不会受到这种问题的困扰。要用语言设计它,支持继承的类应该只能通过引用(而不是通过值)访问。D编程语言具有此属性。
考虑从A派生的类A和类B。如果A部分有一个指针p,而B实例指向B的附加数据,则可能发生内存损坏。然后,当附加数据被切片时,p指向垃圾。
在我看来,除了你自己的类和程序架构/设计不好之外,切片并不是什么问题。
如果我将一个子类对象作为参数传递给一个方法,该方法接受一个超类类型的参数,那么我当然应该意识到这一点,并且知道在内部,被调用的方法只能使用超类(也称为基类)对象。
在我看来,提供一个请求基类的子类会导致子类特定的结果,这似乎只是一个不合理的期望,会导致切片成为一个问题。它要么在方法的使用上设计糟糕,要么子类实现糟糕。我猜这通常是牺牲了良好的OOP设计,而为了方便或提高性能的结果。