Seseorang menyebutkannya di IRC sebagai masalah pemotongan.
Seseorang menyebutkannya di IRC sebagai masalah pemotongan.
Jawaban:
"Slicing" adalah tempat Anda menetapkan objek dari kelas turunan ke instance dari kelas dasar, sehingga kehilangan sebagian informasi - beberapa di antaranya "diiris".
Sebagai contoh,
class A {
int foo;
};
class B : public A {
int bar;
};
Jadi objek tipe B
memiliki dua anggota data, foo
dan bar
.
Maka jika Anda menulis ini:
B b;
A a = b;
Maka informasi di b
tentang anggota bar
hilang di a
.
A a = b;
a
sekarang objek tipe A
yang memiliki salinan B::foo
. Ini akan menjadi kesalahan untuk melemparkannya kembali sekarang saya pikir.
B b1; B b2; A& b2_ref = b2; b2 = b1
. Anda mungkin berpikir Anda telah disalin b1
ke b2
, tetapi Anda belum! Anda telah menyalin sebagian dari b1
ke b2
(bagian b1
yang B
diwarisi dari A
), dan meninggalkan bagian lain yang b2
tidak berubah. b2
sekarang makhluk frankenstein yang terdiri dari beberapa bit b1
diikuti oleh beberapa potongan b2
. Ugh! Downvoting karena saya pikir jawabannya sangat menyesatkan.
B b1; B b2; A& b2_ref = b2; b2_ref = b1
" Masalah sebenarnya terjadi jika Anda " ... berasal dari kelas dengan operator penugasan non-virtual. Apakah A
bahkan dimaksudkan untuk derivasi? Tidak memiliki fungsi virtual. Jika Anda berasal dari suatu jenis, Anda harus berurusan dengan fakta bahwa fungsi anggotanya dapat dipanggil!
Sebagian besar jawaban di sini gagal menjelaskan apa masalah sebenarnya dengan pemotongan. Mereka hanya menjelaskan kasus-kasus jinak yang mengiris, bukan yang berbahaya. Asumsikan, seperti jawaban lain, bahwa Anda berurusan dengan dua kelas A
dan B
, dari mana B
berasal (secara publik) dari A
.
Dalam situasi ini, C ++ memungkinkan Anda memberikan turunan B
ke A
operator penugasan (dan juga ke pembuat salinan). Ini berfungsi karena turunan dari B
dapat dikonversi ke const A&
, yang mana operator penugasan dan copy-constructor mengharapkan argumen mereka.
B b;
A a = b;
Tidak ada hal buruk terjadi di sana - Anda meminta contoh A
yang merupakan salinan B
, dan itulah yang Anda dapatkan. Tentu, a
tidak akan berisi beberapa b
anggota, tetapi bagaimana seharusnya? Lagipula itu A
bukan B
, jadi bahkan belum pernah mendengar tentang anggota ini, apalagi bisa menyimpannya.
B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!
Anda mungkin berpikir itu b2
akan menjadi salinan b1
sesudahnya. Tapi, sayangnya, tidak ! Jika Anda memeriksanya, Anda akan menemukan bahwa itu b2
adalah makhluk Frankenstein, terbuat dari beberapa bongkahan b1
(bongkahan yang B
mewarisi dari A
), dan beberapa bidak b2
(bongkahan yang hanya B
berisi). Aduh!
Apa yang terjadi? Yah, C ++ secara default tidak memperlakukan operator penugasan sebagai virtual
. Dengan demikian, saluran a_ref = b1
akan memanggil operator penugasan A
, bukan dari B
. Ini karena, untuk fungsi-fungsi non-virtual, tipe yang dinyatakan (secara formal: statis ) (yang A&
) menentukan fungsi mana yang dipanggil, sebagai lawan dari tipe aktual (secara formal: dinamis ) (yang akan menjadi B
, karena a_ref
referensi instance dari B
) . Sekarang, A
operator penugasan jelas hanya tahu tentang anggota yang dideklarasikan A
, sehingga hanya akan menyalin yang ada, sehingga anggota yang ditambahkan B
tidak berubah.
Menugaskan hanya untuk bagian-bagian dari suatu objek biasanya tidak masuk akal, namun C ++, sayangnya, tidak menyediakan cara bawaan untuk melarang ini. Anda bisa, bagaimanapun, menggulung sendiri. Langkah pertama adalah membuat operator penugasan virtual . Ini akan menjamin bahwa itu selalu operator penugasan tipe aktual yang dipanggil, bukan tipe yang dideklarasikan . Langkah kedua adalah menggunakan dynamic_cast
untuk memverifikasi bahwa objek yang ditugaskan memiliki tipe yang kompatibel. Langkah ketiga adalah melakukan tugas yang sebenarnya dalam (dilindungi!) Anggota assign()
, karena B
's assign()
mungkin akan ingin menggunakan A
' s assign()
untuk menyalin A
's, anggota.
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
}
};
Perhatikan bahwa, untuk kenyamanan murni, B
's operator=
covariantly menimpa jenis kembali, karena tahu bahwa itu kembali contoh B
.
derived
nilai apa pun dapat diberikan kepada kode yang mengharapkan base
nilai, atau referensi turunan dapat digunakan sebagai referensi dasar. Saya ingin melihat bahasa dengan sistem tipe yang membahas kedua konsep secara terpisah. Ada banyak kasus di mana referensi turunan harus dapat diganti untuk referensi basis, tetapi turunan turunan seharusnya tidak dapat diganti untuk referensi basis; ada juga banyak kasus di mana instance harus dapat dikonversi tetapi referensi tidak boleh diganti.
Jika Anda memiliki kelas dasar A
dan kelas turunan B
, maka Anda dapat melakukan hal berikut.
void wantAnA(A myA)
{
// work with myA
}
B derived;
// work with the object "derived"
wantAnA(derived);
Sekarang metode ini wantAnA
membutuhkan salinan derived
. Namun, objek derived
tidak dapat disalin sepenuhnya, karena kelas B
dapat menemukan variabel anggota tambahan yang tidak ada di kelas dasarnya A
.
Oleh karena itu, untuk memanggil wantAnA
, kompiler akan "memotong" semua anggota tambahan dari kelas turunan. Hasilnya mungkin objek yang tidak ingin Anda buat, karena
A
-object (semua perilaku khusus kelas B
hilang).wantAnA
(sesuai namanya!) Menginginkan A
, maka itulah yang didapatnya. Dan contoh A
, akan, uh, berperilaku seperti A
. Bagaimana itu mengejutkan?
derived
tipe A
. Pengecoran implisit selalu menjadi sumber perilaku tak terduga dalam C ++, karena seringkali sulit untuk memahami dari melihat kode secara lokal bahwa para pemeran berlangsung.
Ini semua adalah jawaban yang bagus. Saya hanya ingin menambahkan contoh eksekusi ketika meneruskan objek dengan nilai vs dengan referensi:
#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());
}
Outputnya adalah:
Call by reference
I am a 'B'
Call by copy
'A' copy constructor
I am an 'A'
Pertandingan ketiga di google untuk "C ++ slicing" memberi saya artikel Wikipedia ini http://en.wikipedia.org/wiki/Object_slicing dan ini (dipanaskan, tetapi beberapa posting pertama menjelaskan masalahnya): http://bytes.com/ forum / thread163565.html
Jadi ketika Anda menetapkan objek subclass ke kelas super. Superclass tidak tahu apa-apa tentang informasi tambahan di subkelas, dan tidak punya ruang untuk menyimpannya, sehingga informasi tambahan akan "dipotong".
Jika tautan itu tidak memberikan info yang cukup untuk "jawaban yang baik", harap edit pertanyaan Anda untuk memberi tahu kami apa yang Anda cari.
Masalah mengiris sangat serius karena dapat mengakibatkan kerusakan memori, dan sangat sulit untuk menjamin suatu program tidak menderita karenanya. Untuk mendesainnya dari bahasa, kelas yang mendukung pewarisan harus dapat diakses hanya dengan referensi (bukan berdasarkan nilai). Bahasa pemrograman D memiliki properti ini.
Pertimbangkan kelas A, dan kelas B yang berasal dari A. Korupsi memori dapat terjadi jika bagian A memiliki pointer p, dan instance B yang menunjuk p ke data tambahan B. Kemudian, ketika data tambahan diiris, p menunjuk ke sampah.
Derived
secara implisit dapat dikonversi menjadi Base
.) Ini jelas bertentangan dengan Prinsip Terbuka-Tertutup, dan beban pemeliharaan yang besar.
Dalam C ++, objek kelas turunan dapat ditugaskan ke objek kelas dasar, tetapi cara lain tidak mungkin.
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
}
Mengiris objek terjadi ketika objek kelas turunan ditugaskan ke objek kelas dasar, atribut tambahan dari objek kelas turunan diiris untuk membentuk objek kelas dasar.
Masalah mengiris dalam C ++ muncul dari semantik nilai objeknya, yang sebagian besar tetap karena kompatibilitas dengan C struct. Anda perlu menggunakan referensi eksplisit atau sintaks penunjuk untuk mencapai perilaku objek "normal" yang ditemukan di sebagian besar bahasa lain yang melakukan objek, yaitu objek selalu diedarkan dengan referensi.
Jawaban singkatnya adalah bahwa Anda mengiris objek dengan menetapkan objek yang diturunkan ke objek dasar dengan nilai , yaitu objek yang tersisa hanya bagian dari objek yang diturunkan. Untuk menjaga semantik nilai, mengiris adalah perilaku yang wajar dan memiliki kegunaan yang relatif jarang, yang tidak ada di sebagian besar bahasa lain. Beberapa orang menganggapnya sebagai fitur C ++, sementara banyak yang menganggapnya sebagai salah satu keanehan / kesalahan dari C ++.
struct
, kompatibilitas, atau non-sense lain imam setiap OOP acak kepada Anda.
Base
harus mengambil sizeof(Base)
byte dalam memori, dengan kemungkinan penyelarasan, mungkin, itulah sebabnya "penugasan" (on-stack-copy ) tidak akan menyalin anggota kelas yang diturunkan, offset mereka di luar ukuran. Untuk menghindari "kehilangan data", cukup gunakan pointer, seperti orang lain, karena memori pointer tetap di tempat dan ukuran, sedangkan tumpukan sangat mudah berubah
Jadi ... Mengapa kehilangan informasi turunannya buruk? ... karena penulis kelas turunan mungkin telah mengubah representasi sehingga memotong informasi tambahan mengubah nilai yang diwakili oleh objek. Ini bisa terjadi jika kelas turunannya jika digunakan untuk menyimpan representasi yang lebih efisien untuk operasi tertentu, tetapi mahal untuk mengubah kembali ke representasi basis.
Juga berpikir seseorang juga harus menyebutkan apa yang harus Anda lakukan untuk menghindari pemotongan ... Dapatkan salinan Standar C ++ Coding, 101 pedoman pedoman aturan, dan praktik terbaik. Berurusan dengan mengiris adalah # 54.
Ini menyarankan pola yang agak canggih untuk sepenuhnya menangani masalah ini: memiliki konstruktor salinan yang dilindungi, DoClone virtual murni yang dilindungi, dan Klon publik dengan pernyataan yang akan memberi tahu Anda jika kelas turunan (lebih lanjut) gagal menerapkan DoClone dengan benar. (Metode Klon membuat salinan yang dalam dari objek polimorfik.)
Anda juga dapat menandai konstruktor salinan pada basis eksplisit yang memungkinkan untuk mengiris eksplisit jika diinginkan.
1. DEFINISI MASALAH PERLENGKAPAN
Jika D adalah kelas turunan dari kelas dasar B, maka Anda dapat menetapkan objek bertipe Derived ke variabel (atau parameter) dari tipe Basis.
CONTOH
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
Meskipun penugasan di atas diizinkan, nilai yang diberikan pada variabel peliharaan kehilangan bidang pembiakannya. Ini disebut masalah mengiris .
2. CARA MEMPERBAIKI MASALAH PELISING
Untuk mengatasi masalah, kami menggunakan pointer ke variabel dinamis.
CONTOH
Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed;
Dalam hal ini, tidak ada anggota data atau fungsi anggota dari variabel dinamis yang ditunjukkan oleh ptrD (objek kelas descendant) yang akan hilang. Selain itu, jika Anda perlu menggunakan fungsi, fungsi tersebut harus merupakan fungsi virtual.
dog
itu bukan bagian dari kelas Pet
( breed
anggota data) tidak disalin dalam variabel pet
? Kode hanya tertarik pada Pet
anggota data - rupanya. Mengiris jelas merupakan "masalah" jika tidak diinginkan, tetapi saya tidak melihatnya di sini.
((Dog *)ptrP)
" Saya sarankan menggunakanstatic_cast<Dog*>(ptrP)
Dog::breed
) tidak mungkin ERROR terkait dengan SLICING?
Menurut saya, mengiris bukanlah masalah besar selain ketika kelas dan program Anda sendiri dirancang / dirancang dengan buruk.
Jika saya melewatkan objek subclass sebagai parameter untuk metode, yang mengambil parameter dari jenis superclass, saya tentu harus mengetahui hal itu dan mengetahui secara internal, metode yang dipanggil akan bekerja dengan objek superclass (alias baseclass) saja.
Bagi saya hanya harapan yang tidak masuk akal bahwa memberikan subclass di mana sebuah baseclass diminta, entah bagaimana akan menghasilkan hasil spesifik subclass, akan menyebabkan pengirisan menjadi masalah. Entah itu desain yang buruk dalam penggunaan metode atau implementasi subclass yang buruk. Saya menduga itu biasanya hasil dari mengorbankan desain OOP yang baik demi keuntungan atau kinerja.
OK, saya akan mencobanya setelah membaca banyak posting yang menjelaskan pemotongan objek tetapi tidak bagaimana itu menjadi bermasalah.
Skenario ganas yang dapat menyebabkan memori rusak adalah sebagai berikut:
Mengiris berarti bahwa data yang ditambahkan oleh subclass dibuang ketika suatu objek dari subclass dilewatkan atau dikembalikan dengan nilai atau dari suatu fungsi yang mengharapkan objek kelas dasar.
Penjelasan: Pertimbangkan deklarasi kelas berikut:
class baseclass
{
...
baseclass & operator =(const baseclass&);
baseclass(const baseclass&);
}
void function( )
{
baseclass obj1=m;
obj1=m;
}
Karena fungsi copy baseclass tidak tahu apa-apa tentang turunan, hanya bagian dasar turunan yang disalin. Ini biasa disebut 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;
}
ketika objek kelas turunan ditugaskan ke objek kelas dasar, atribut tambahan dari objek kelas turunan dipotong (dibuang) dari objek kelas dasar.
class Base {
int x;
};
class Derived : public Base {
int z;
};
int main()
{
Derived d;
Base b = d; // Object Slicing, z of d is sliced off
}
Ketika objek kelas turunan ditugaskan ke objek kelas dasar, semua anggota objek kelas turunan disalin ke objek kelas dasar kecuali anggota yang tidak ada di kelas dasar. Anggota-anggota ini diiris oleh kompiler. Ini disebut Object Slicing.
Berikut ini sebuah Contoh:
#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;
}
Ini akan menghasilkan:
[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'
Saya hanya berlari melintasi masalah pemotongan dan segera mendarat di sini. Jadi izinkan saya menambahkan dua sen saya untuk ini.
Mari kita ambil contoh dari "kode produksi" (atau sesuatu yang hampir mirip):
Katakanlah kita memiliki sesuatu yang mengirimkan tindakan. UI pusat kendali misalnya.
UI ini perlu mendapatkan daftar hal-hal yang saat ini dapat dikirim. Jadi kami mendefinisikan kelas yang berisi informasi pengiriman. Sebut saja Action
. Jadi Action
memiliki beberapa variabel anggota. Untuk kesederhanaan, kita hanya memiliki 2, menjadi a std::string name
dan a std::function<void()> f
. Kemudian memiliki void activate()
yang hanya mengeksekusi f
anggota.
Jadi UI std::vector<Action>
disediakan. Bayangkan beberapa fungsi seperti:
void push_back(Action toAdd);
Sekarang kami telah menetapkan bagaimana tampilannya dari perspektif UI. Tidak masalah sejauh ini. Tetapi beberapa pria lain yang bekerja pada proyek ini tiba-tiba memutuskan bahwa ada tindakan khusus yang memerlukan lebih banyak informasi di Action
objek. Untuk alasan apa pun. Itu juga bisa diselesaikan dengan tangkapan lambda. Contoh ini tidak diambil 1-1 dari kode.
Jadi pria itu berasal dari Action
untuk menambahkan rasa sendiri.
Dia melewati sebuah instance dari kelas buatannya ke rumah push_back
tetapi kemudian program menjadi berantakan.
Jadi apa yang terjadi?
Seperti yang mungkin sudah Anda duga: objek telah diiris.
Informasi tambahan dari instance telah hilang, dan f
sekarang rentan terhadap perilaku yang tidak terdefinisi.
Saya harap contoh ini membawa cahaya bagi orang-orang yang tidak bisa membayangkan hal-hal ketika berbicara tentang A
dan B
diturunkan dalam beberapa cara.