Seseorang menyebutkannya di IRC sebagai masalah pemotongan.
c++
inheritance
c++-faq
object-slicing
Frankomania
sumber
sumber
A a = b;
a
sekarang objek tipeA
yang memiliki salinanB::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 disalinb1
keb2
, tetapi Anda belum! Anda telah menyalin sebagian darib1
keb2
(bagianb1
yangB
diwarisi dariA
), dan meninggalkan bagian lain yangb2
tidak berubah.b2
sekarang makhluk frankenstein yang terdiri dari beberapa bitb1
diikuti oleh beberapa potonganb2
. 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. ApakahA
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
danB
, dari manaB
berasal (secara publik) dariA
.Dalam situasi ini, C ++ memungkinkan Anda memberikan turunan
B
keA
operator penugasan (dan juga ke pembuat salinan). Ini berfungsi karena turunan dariB
dapat dikonversi keconst A&
, yang mana operator penugasan dan copy-constructor mengharapkan argumen mereka.Kasus jinak
Tidak ada hal buruk terjadi di sana - Anda meminta contoh
A
yang merupakan salinanB
, dan itulah yang Anda dapatkan. Tentu,a
tidak akan berisi beberapab
anggota, tetapi bagaimana seharusnya? Lagipula ituA
bukanB
, jadi bahkan belum pernah mendengar tentang anggota ini, apalagi bisa menyimpannya.Kasus berbahaya
Anda mungkin berpikir itu
b2
akan menjadi salinanb1
sesudahnya. Tapi, sayangnya, tidak ! Jika Anda memeriksanya, Anda akan menemukan bahwa itub2
adalah makhluk Frankenstein, terbuat dari beberapa bongkahanb1
(bongkahan yangB
mewarisi dariA
), dan beberapa bidakb2
(bongkahan yang hanyaB
berisi). Aduh!Apa yang terjadi? Yah, C ++ secara default tidak memperlakukan operator penugasan sebagai
virtual
. Dengan demikian, salurana_ref = b1
akan memanggil operator penugasanA
, bukan dariB
. Ini karena, untuk fungsi-fungsi non-virtual, tipe yang dinyatakan (secara formal: statis ) (yangA&
) menentukan fungsi mana yang dipanggil, sebagai lawan dari tipe aktual (secara formal: dinamis ) (yang akan menjadiB
, karenaa_ref
referensi instance dariB
) . Sekarang,A
operator penugasan jelas hanya tahu tentang anggota yang dideklarasikanA
, sehingga hanya akan menyalin yang ada, sehingga anggota yang ditambahkanB
tidak berubah.Sebuah solusi
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!) Anggotaassign()
, karenaB
'sassign()
mungkin akan ingin menggunakanA
' sassign()
untuk menyalinA
's, anggota.Perhatikan bahwa, untuk kenyamanan murni,
B
'soperator=
covariantly menimpa jenis kembali, karena tahu bahwa itu kembali contohB
.sumber
derived
nilai apa pun dapat diberikan kepada kode yang mengharapkanbase
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 turunanB
, maka Anda dapat melakukan hal berikut.Sekarang metode ini
wantAnA
membutuhkan salinanderived
. Namun, objekderived
tidak dapat disalin sepenuhnya, karena kelasB
dapat menemukan variabel anggota tambahan yang tidak ada di kelas dasarnyaA
.Oleh karena itu, untuk memanggil
wantAnA
, kompiler akan "memotong" semua anggota tambahan dari kelas turunan. Hasilnya mungkin objek yang tidak ingin Anda buat, karenaA
-object (semua perilaku khusus kelasB
hilang).sumber
wantAnA
(sesuai namanya!) MenginginkanA
, maka itulah yang didapatnya. Dan contohA
, akan, uh, berperilaku sepertiA
. Bagaimana itu mengejutkan?derived
tipeA
. 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:
Outputnya adalah:
sumber
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.
sumber
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.
sumber
Derived
secara implisit dapat dikonversi menjadiBase
.) 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.
Mengiris objek terjadi ketika objek kelas turunan ditugaskan ke objek kelas dasar, atribut tambahan dari objek kelas turunan diiris untuk membentuk objek kelas dasar.
sumber
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 ++.
sumber
struct
, kompatibilitas, atau non-sense lain imam setiap OOP acak kepada Anda.Base
harus mengambilsizeof(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 berubahJadi ... 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.
sumber
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
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
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.
sumber
dog
itu bukan bagian dari kelasPet
(breed
anggota data) tidak disalin dalam variabelpet
? Kode hanya tertarik padaPet
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.
sumber
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:
sumber
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:
Karena fungsi copy baseclass tidak tahu apa-apa tentang turunan, hanya bagian dasar turunan yang disalin. Ini biasa disebut slicing.
sumber
sumber
ketika objek kelas turunan ditugaskan ke objek kelas dasar, atribut tambahan dari objek kelas turunan dipotong (dibuang) dari objek kelas dasar.
sumber
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:
Ini akan menghasilkan:
sumber
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
. JadiAction
memiliki beberapa variabel anggota. Untuk kesederhanaan, kita hanya memiliki 2, menjadi astd::string name
dan astd::function<void()> f
. Kemudian memilikivoid activate()
yang hanya mengeksekusif
anggota.Jadi UI
std::vector<Action>
disediakan. Bayangkan beberapa fungsi seperti: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
danB
diturunkan dalam beberapa cara.sumber