Object Slicing คืออะไร?
มีคนพูดถึงเรื่องนี้ใน IRC ว่าเป็นปัญหาการแบ่งส่วน
คำตอบ
"Slicing" คือการที่คุณกำหนดอ็อบเจ็กต์ของคลาสที่ได้รับมาให้กับอินสแตนซ์ของคลาสพื้นฐานซึ่งจะทำให้ข้อมูลบางส่วนหายไป - บางส่วนจะถูก "แบ่งส่วน" ออกไป
ตัวอย่างเช่น,
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
ดำเนินการกำหนด 's (และยัง constructor สำเนา) สิ่งนี้ใช้ได้ผลเนื่องจากB
สามารถแปลงอินสแตนซ์เป็น a 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
ภายหลัง แต่อนิจจาไม่ใช่ ! หากคุณตรวจสอบคุณจะพบว่า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
's 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
}
};
โปรดทราบว่าเพื่อความสะดวกอย่างB
แท้จริงoperator=
จะแทนที่ประเภทการส่งคืนด้วยความเต็มใจเนื่องจากทราบว่ากำลังส่งคืนอินสแตนซ์ของB
.
หากคุณมีคลาสพื้นฐานA
และคลาสที่ได้รับB
คุณสามารถทำสิ่งต่อไปนี้ได้
void wantAnA(A myA)
{
// work with myA
}
B derived;
// work with the object "derived"
wantAnA(derived);
Now the method wantAnA
needs a copy of derived
. However, the object derived
cannot be copied completely, as the class B
could invent additional member variables which are not in its base class A
.
Therefore, to call wantAnA
, the compiler will "slice off" all additional members of the derived class. The result might be an object you did not want to create, because
- it may be incomplete,
- it behaves like an
A
-object (all special behaviour of the classB
is lost).
These are all good answers. I would just like to add an execution example when passing objects by value vs by reference:
#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());
}
The output is:
Call by reference
I am a 'B'
Call by copy
'A' copy constructor
I am an 'A'
Third match in google for "C++ slicing" gives me this Wikipedia article http://en.wikipedia.org/wiki/Object_slicing and this (heated, but the first few posts define the problem) : http://bytes.com/forum/thread163565.html
So it's when you assign an object of a subclass to the super class. The superclass knows nothing of the additional information in the subclass, and hasn't got room to store it, so the additional information gets "sliced off".
If those links don't give enough info for a "good answer" please edit your question to let us know what more you're looking for.
The slicing problem is serious because it can result in memory corruption, and it is very difficult to guarantee a program does not suffer from it. To design it out of the language, classes that support inheritance should be accessible by reference only (not by value). The D programming language has this property.
Consider class A, and class B derived from A. Memory corruption can happen if the A part has a pointer p, and a B instance that points p to B's additional data. Then, when the additional data gets sliced off, p is pointing to garbage.
In C++, a derived class object can be assigned to a base class object, but the other way is not possible.
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
}
Object slicing happens when a derived class object is assigned to a base class object, additional attributes of a derived class object are sliced off to form the base class object.
So ... Why is losing the derived information bad? ... because the author of the derived class may have changed the representation such that slicing off the extra information changes the value being represented by the object. This can happen if the derived class if used to cache a representation that is more efficient for certain operations, but expensive to transform back to the base representation.
Also thought someone should also mention what you should do to avoid slicing... Get a copy of C++ Coding Standards, 101 rules guidlines, and best practices. Dealing with slicing is #54.
It suggests a somewhat sophisticated pattern to fully deal with the issue: have a protected copy constructor, a protected pure virtual DoClone, and a public Clone with an assert which will tell you if a (further) derived class failed to implement DoClone correctly. (The Clone method makes a proper deep copy of the polymorphic object.)
You can also mark the copy constructor on the base explicit which allows for explicit slicing if it is desired.
The slicing problem in C++ arises from the value semantics of its objects, which remained mostly due to compatibility with C structs. You need to use explicit reference or pointer syntax to achieve "normal" object behavior found in most other languages that do objects, i.e., objects are always passed around by reference.
The short answers is that you slice the object by assigning a derived object to a base object by value, i.e. the remaining object is only a part of the derived object. In order to preserve value semantics, slicing is a reasonable behavior and has its relatively rare uses, which doesn't exist in most other languages. Some people consider it a feature of C++, while many considered it one of the quirks/misfeatures of C++.
1. THE DEFINITION OF SLICING PROBLEM
If D is a derived class of the base class B, then you can assign an object of type Derived to a variable (or parameter) of type Base.
EXAMPLE
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
Although the above assignment is allowed, the value that is assigned to the variable pet loses its breed field. This is called the slicing problem.
2. HOW TO FIX THE SLICING PROBLEM
To defeat the problem, we use pointers to dynamic variables.
EXAMPLE
Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed;
In this case, none of the data members or member functions of the dynamic variable being pointed to by ptrD (descendant class object) will be lost. In addition, if you need to use functions, the function must be a virtual function.
It seems to me, that slicing isn't so much a problem other than when your own classes and program are poorly architected/designed.
If I pass a subclass object in as a parameter to a method, which takes a parameter of type superclass, I should certainly be aware of that and know the internally, the called method will be working with the superclass (aka baseclass) object only.
It seems to me only the unreasonable expectation that providing a subclass where a baseclass is requested, would somehow result in subclass specific results, would cause slicing to be a problem. Its either poor design in the use of the method or a poor subclass implementation. I'm guessing its usually the result of sacrificing good OOP design in favor of expediency or performance gains.
OK, I'll give it a try after reading many posts explaining object slicing but not how it becomes problematic.
The vicious scenario that can result in memory corruption is the following:
- Class provides (accidentally, possibly compiler-generated) assignment on a polymorphic base class.
- Client copies and slices an instance of a derived class.
- Client calls a virtual member function that accesses the sliced-off state.
Slicing means that the data added by a subclass are discarded when an object of the subclass is passed or returned by value or from a function expecting a base class object.
Explanation: Consider the following class declaration:
class baseclass
{
...
baseclass & operator =(const baseclass&);
baseclass(const baseclass&);
}
void function( )
{
baseclass obj1=m;
obj1=m;
}
As baseclass copy functions don't know anything about the derived only the base part of the derived is copied. This is commonly referred to as slicing.
class A
{
int x;
};
class B
{
B( ) : x(1), c('a') { }
int x;
char c;
};
int main( )
{
A a;
B b;
a = b; // b.c == 'a' is "sliced" off
return 0;
}
I see all the answers mention when object slicing happens when data members are sliced. Here I give an example that the methods are not overridden:
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 (object b) is derived from A (object a1 and a2). b and a1, as we expect, call their member function. But from polymorphism viewpoint we don’t expect a2, which is assigned by b, to not be overridden. Basically, a2 only saves A-class part of b and that is object slicing in C++.
To solve this problem, a reference or pointer should be used
A& a2=b;
a2.Say(); // I am B
or
A* a2 = &b;
a2->Say(); // I am B
For more details see my post