Dav*_*ben 586
"切片"是指将派生类的对象分配给基类的实例,从而丢失部分信息 - 其中一些信息被"切片"掉.
例如,
class A {
int foo;
};
class B : public A {
int bar;
};
Run Code Online (Sandbox Code Playgroud)
所以类型的对象B有两个数据成员,foo和bar.
然后,如果你写这个:
B b;
A a = b;
Run Code Online (Sandbox Code Playgroud)
然后b关于成员的信息bar丢失了a.
fgp*_*fgp 476
这里的大多数答案都无法解释切片的实际问题是什么.他们只解释切片的良性情况,而不是危险的情况.假设,像其他的答案,你正在处理两班A和B,其中B导出(公开)的A.
在这种情况下,C++,您可以通过一个实例B来 A的赋值运算符(同时也拷贝构造函数).这是有效的,因为一个实例B可以转换为a const A&,这就是赋值运算符和复制构造函数期望它们的参数.
B b;
A a = b;
Run Code Online (Sandbox Code Playgroud)
没有什么不好的事情发生在那里 - 你要求一个实例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!
Run Code Online (Sandbox Code Playgroud)
您可能认为这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验证分配的对象是否具有兼容类型.第三步是在(受保护!)成员中进行实际任务assign(),因为B他们assign()可能想要使用A's 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
}
};
Run Code Online (Sandbox Code Playgroud)
请注意,为了方便起见,它们B会operator=共同覆盖返回类型,因为它知道它正在返回一个实例B.
Bla*_*ack 152
如果您有基类A和派生类B,则可以执行以下操作.
void wantAnA(A myA)
{
// work with myA
}
B derived;
// work with the object "derived"
wantAnA(derived);
Run Code Online (Sandbox Code Playgroud)
现在该方法wantAnA需要一份副本derived.但是,该对象derived无法完全复制,因为该类B可能会发明不在其基类中的其他成员变量A.
因此,要调用wantAnA,编译器将"切掉"派生类的所有其他成员.结果可能是您不想创建的对象,因为
A(该类的所有特殊行为B都会丢失).geh*_*geh 38
这些都是很好的答案.我想在按值和引用传递对象时添加一个执行示例:
#include <iostream>
using namespace std;
// Base class
class A {
public:
A() {}
A(const A& a) {
cout << "'A' copy constructor" << endl;
}
virtual void run() const { cout << "I am an 'A'" << endl; }
};
// Derived class
class B: public A {
public:
B():A() {}
B(const B& a):A(a) {
cout << "'B' copy constructor" << endl;
}
virtual void run() const { cout << "I am a 'B'" << endl; }
};
void g(const A & a) {
a.run();
}
void h(const A a) {
a.run();
}
int main() {
cout << "Call by reference" << endl;
g(B());
cout << endl << "Call by copy" << endl;
h(B());
}
Run Code Online (Sandbox Code Playgroud)
输出是:
Call by reference
I am a 'B'
Call by copy
'A' copy constructor
I am an 'A'
Run Code Online (Sandbox Code Playgroud)
The*_*aul 31
谷歌"C++切片"的第三场比赛给了我这篇维基百科文章http://en.wikipedia.org/wiki/Object_slicing和这个(加热,但前几个帖子定义了问题):http://bytes.com/论坛/ thread163565.html
所以当你将一个子类的对象分配给超类时.超类对子类中的附加信息一无所知,也没有足够的空间来存储它,因此附加信息会被"切掉".
如果这些链接没有为"正确答案"提供足够的信息,请编辑您的问题,让我们知道您还在寻找什么.
Wal*_*ght 28
切片问题很严重,因为它可能导致内存损坏,并且很难保证程序不会受到影响.要使用该语言进行设计,支持继承的类应该只能通过引用访问(而不是通过值).D编程语言具有此属性.
考虑A类,从B派生B类.如果A部分有一个指针p,则会发生内存损坏,而B实例将p指向B的附加数据.然后,当附加数据被切掉时,p指向垃圾.
小智 9
在C++中,派生类对象可以分配给基类对象,但另一种方法是不可能的.
class Base { int x, y; };
class Derived : public Base { int z, w; };
int main()
{
Derived d;
Base b = d; // Object Slicing, z and w of d are sliced off
}
Run Code Online (Sandbox Code Playgroud)
当派生类对象被分配给基类对象时,会发生对象切片,派生类对象的其他属性将被切除以形成基类对象.
C++中的切片问题源于其对象的值语义,这主要是由于与C结构的兼容性.您需要使用显式引用或指针语法来实现在执行对象的大多数其他语言中找到的"正常"对象行为,即,对象始终通过引用传递.
简短的答案是通过按值将派生对象分配给基础对象来切片对象,即剩余对象只是派生对象的一部分.为了保留价值语义,切片是一种合理的行为,并且具有相对罕见的用途,这在大多数其他语言中是不存在的.有些人认为它是C++的一个特性,而许多人认为它是C++的怪癖/错误特征之一.
那么......为什么丢失衍生信息不好?...因为派生类的作者可能已经更改了表示,因此切掉额外信息会更改对象所表示的值.如果派生类用于缓存对某些操作更有效但对转换回基本表示而言代价高的表示,则会发生这种情况.
还想到有人还应该提到你应该做些什么以避免切片......获取C++编码标准,101规则指南和最佳实践的副本.处理切片是#54.
它提出了一个有点复杂的模式来完全处理这个问题:拥有一个受保护的复制构造函数,一个受保护的纯虚拟DoClone,以及一个带有断言的公共克隆,它将告诉您(某个)派生类是否未能正确实现DoClone.(克隆方法对多态对象进行了适当的深层复制.)
您还可以在基本显式标记复制构造函数,如果需要,可以显式切片.
1.切片问题的定义
如果D是基类B的派生类,则可以将Derived类型的对象分配给Base类型的变量(或参数).
例
class Pet
{
public:
string name;
};
class Dog : public Pet
{
public:
string breed;
};
int main()
{
Dog dog;
Pet pet;
dog.name = "Tommy";
dog.breed = "Kangal Dog";
pet = dog;
cout << pet.breed; //ERROR
Run Code Online (Sandbox Code Playgroud)
尽管允许上述赋值,但分配给变量pet的值会丢失其品种字段.这称为切片问题.
2.如何修复切片问题
为了解决这个问题,我们使用指向动态变量的指针.
例
Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed;
Run Code Online (Sandbox Code Playgroud)
在这种情况下,ptrD(后代类对象)指向的动态变量的数据成员或成员函数都不会丢失.另外,如果需要使用函数,该函数必须是虚函数.
当数据成员被切片时发生对象切片时,我看到所有提到的答案。在这里,我举了一个方法不被覆盖的例子:
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???
}
Run Code Online (Sandbox Code Playgroud)
B(对象b)源自A(对象a1和a2)。b 和 a1,如我们所料,调用它们的成员函数。但是从多态性的角度来看,我们不希望由 b 分配的 a2 不会被覆盖。基本上,a2 只保存 b 的 A 类部分,即 C++ 中的对象切片。
要解决这个问题,应该使用引用或指针
A& a2=b;
a2.Say(); // I am B
Run Code Online (Sandbox Code Playgroud)
或者
A* a2 = &b;
a2->Say(); // I am B
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
172685 次 |
| 最近记录: |