Apa itu objek slicing?

Jawaban:

609

"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 Bmemiliki dua anggota data, foodan bar.

Maka jika Anda menulis ini:

B b;

A a = b;

Maka informasi di btentang anggota barhilang di a.

David Dibben
sumber
66
Sangat informatif, tetapi lihat stackoverflow.com/questions/274626#274636 untuk contoh bagaimana pengirisan terjadi selama pemanggilan metode (yang menggarisbawahi bahaya sedikit lebih baik daripada contoh penugasan biasa).
Blair Conrad
55
Menarik. Saya telah memprogram dalam C ++ selama 15 tahun dan masalah ini tidak pernah terjadi pada saya, karena saya selalu melewati objek dengan referensi sebagai masalah efisiensi dan gaya pribadi. Tunjukkan bagaimana kebiasaan yang baik dapat membantu Anda.
Karl Bielefeldt
10
@ Feliks Terima kasih tetapi saya tidak berpikir casting kembali (karena bukan aritmatika pointer) akan berfungsi, A a = b; asekarang objek tipe Ayang memiliki salinan B::foo. Ini akan menjadi kesalahan untuk melemparkannya kembali sekarang saya pikir.
37
Ini bukan "mengiris", atau setidaknya varian yang jinak. Masalah sebenarnya terjadi jika Anda melakukannya B b1; B b2; A& b2_ref = b2; b2 = b1. Anda mungkin berpikir Anda telah disalin b1ke b2, tetapi Anda belum! Anda telah menyalin sebagian dari b1ke b2(bagian b1yang Bdiwarisi dari A), dan meninggalkan bagian lain yang b2tidak berubah. b2sekarang makhluk frankenstein yang terdiri dari beberapa bit b1diikuti oleh beberapa potongan b2. Ugh! Downvoting karena saya pikir jawabannya sangat menyesatkan.
fgp
24
@ fgp Komentar Anda harus berbunyi 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 Abahkan dimaksudkan untuk derivasi? Tidak memiliki fungsi virtual. Jika Anda berasal dari suatu jenis, Anda harus berurusan dengan fakta bahwa fungsi anggotanya dapat dipanggil!
curiousguy
510

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 Adan B, dari mana Bberasal (secara publik) dari A.

Dalam situasi ini, C ++ memungkinkan Anda memberikan turunan Bke Aoperator penugasan (dan juga ke pembuat salinan). Ini berfungsi karena turunan dari Bdapat dikonversi ke const A&, yang mana operator penugasan dan copy-constructor mengharapkan argumen mereka.

Kasus jinak

B b;
A a = b;

Tidak ada hal buruk terjadi di sana - Anda meminta contoh Ayang merupakan salinan B, dan itulah yang Anda dapatkan. Tentu, atidak akan berisi beberapa banggota, tetapi bagaimana seharusnya? Lagipula itu Abukan B, jadi bahkan belum pernah mendengar tentang anggota ini, apalagi bisa menyimpannya.

Kasus berbahaya

B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!

Anda mungkin berpikir itu b2akan menjadi salinan b1sesudahnya. Tapi, sayangnya, tidak ! Jika Anda memeriksanya, Anda akan menemukan bahwa itu b2adalah makhluk Frankenstein, terbuat dari beberapa bongkahan b1(bongkahan yang Bmewarisi dari A), dan beberapa bidak b2(bongkahan yang hanya Bberisi). Aduh!

Apa yang terjadi? Yah, C ++ secara default tidak memperlakukan operator penugasan sebagai virtual. Dengan demikian, saluran a_ref = b1akan 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_refreferensi instance dari B) . Sekarang, Aoperator penugasan jelas hanya tahu tentang anggota yang dideklarasikan A, sehingga hanya akan menyalin yang ada, sehingga anggota yang ditambahkan Btidak 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_castuntuk 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.

fgp
sumber
12
IMHO, masalahnya adalah bahwa ada dua jenis yang dapat disubstitusikan yang dapat disiratkan oleh warisan: baik derivednilai apa pun dapat diberikan kepada kode yang mengharapkan basenilai, 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.
supercat
16
Saya tidak mengerti apa yang begitu buruk dalam kasus "pengkhianatan" Anda. Anda menyatakan bahwa Anda ingin: 1) mendapatkan referensi ke objek kelas A dan 2) melemparkan objek b1 ke kelas A dan menyalin barang-barangnya ke referensi kelas A. Apa yang sebenarnya salah di sini adalah logika yang tepat di belakang kode yang diberikan. Dengan kata lain, Anda mengambil bingkai gambar kecil (A), meletakkannya di atas gambar yang lebih besar (B) dan Anda melukis melalui bingkai itu, mengeluh kemudian bahwa gambar Anda yang lebih besar sekarang terlihat jelek :) Tapi jika kita hanya mempertimbangkan area berbingkai itu, itu terlihat cukup bagus, seperti yang diinginkan pelukis, kan? :)
Mladen B.
12
Masalahnya, secara berbeda, bahwa C ++ secara default mengasumsikan jenis substitusi yang sangat kuat - ini membutuhkan operasi kelas dasar untuk bekerja dengan benar pada instance subkelas. Dan itu bahkan untuk operasi yang dikompilasi otomatis oleh kompiler seperti penugasan. Jadi tidak cukup untuk tidak mengacaukan operasi Anda sendiri dalam hal ini, Anda juga harus secara eksplisit menonaktifkan yang salah yang dihasilkan oleh kompiler. Atau tentu saja, menjauhlah dari warisan publik, yang biasanya merupakan saran yang baik ;-)
fgp
14
Pendekatan umum lainnya adalah dengan cukup menonaktifkan copy dan operator penugasan. Untuk kelas dalam hierarki warisan, biasanya tidak ada alasan untuk menggunakan nilai alih-alih referensi atau pointer.
Siyuan Ren
13
Apa itu? Saya tidak tahu operator dapat ditandai virtual
paulm
154

Jika Anda memiliki kelas dasar Adan 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 wantAnAmembutuhkan salinan derived. Namun, objek derivedtidak dapat disalin sepenuhnya, karena kelas Bdapat 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

  • mungkin tidak lengkap,
  • itu berperilaku seperti A-object (semua perilaku khusus kelas Bhilang).
Hitam
sumber
41
C ++ bukan Java! Jika wantAnA(sesuai namanya!) Menginginkan A, maka itulah yang didapatnya. Dan contoh A, akan, uh, berperilaku seperti A. Bagaimana itu mengejutkan?
fgp
83
@ fgp: Mengejutkan, karena Anda tidak memberikan nilai A ke fungsi.
Black
10
@ fgp: Perilaku ini mirip. Namun, untuk programmer C ++ rata-rata mungkin kurang jelas. Sejauh yang saya mengerti pertanyaan itu, tidak ada yang "mengeluh". Ini hanya tentang bagaimana kompiler menangani situasi. Imho, lebih baik untuk menghindari mengiris sama sekali dengan melewati (const) referensi.
Hitam
9
@ Thomas Tidak, saya tidak akan membuang warisan, tetapi menggunakan referensi. Jika tanda tangan wantAnA tidak berlaku wantAnA (const A & myA) , maka tidak ada slicing. Sebagai gantinya, referensi hanya baca untuk objek pemanggil dilewatkan.
Hitam
14
masalahnya kebanyakan pada casting otomatis yang dilakukan oleh compiler dari derivedtipe 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.
pqnet
41

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'
geh
sumber
Halo. Jawaban yang bagus tetapi saya punya satu pertanyaan. Jika saya melakukan sesuatu seperti ini ** dev d; base * b = & d; ** Pengirisan juga terjadi?
Adrian
@Adrian Jika Anda memperkenalkan beberapa fungsi anggota baru atau variabel anggota di kelas turunan maka itu tidak dapat diakses dari pointer kelas dasar secara langsung. Namun Anda masih dapat mengaksesnya dari dalam fungsi virtual kelas dasar yang kelebihan beban. Lihat ini: godbolt.org/z/LABx33
Vishal Sharma
30

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.

Pola Dasar Paul
sumber
29

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.

Walter Bright
sumber
3
Tolong jelaskan bagaimana kerusakan memori bisa terjadi.
foraidt
4
Saya lupa bahwa copy ctor akan mengatur ulang vptr, kesalahan saya. Tetapi Anda masih bisa mendapatkan korupsi jika A memiliki pointer, dan B menetapkan itu untuk menunjuk ke bagian B yang akan dipotong.
Walter Bright
18
Masalah ini tidak hanya terbatas pada pemotongan. Setiap kelas yang mengandung pointer akan memiliki perilaku yang meragukan dengan operator penugasan default dan copy-constructor.
Weeble
2
@ Weeble - Itulah sebabnya Anda menimpa destruktor default, operator penugasan dan copy-constructor dalam kasus ini.
Bjarke Freund-Hansen
7
@ Weeble: Apa yang membuat objek mengiris lebih buruk daripada perbaikan pointer umum adalah untuk memastikan Anda telah mencegah pengirisan terjadi, kelas dasar harus menyediakan konstruktor konversi untuk setiap kelas turunan . (Mengapa? Setiap kelas turunan yang terlewatkan rentan untuk diambil oleh copy ctor kelas dasar, karena Derivedsecara implisit dapat dikonversi menjadi Base.) Ini jelas bertentangan dengan Prinsip Terbuka-Tertutup, dan beban pemeliharaan yang besar.
j_random_hacker
11

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.

Kartik Maheshwari
sumber
8

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 ++.

ididak
sumber
5
" " normal "perilaku objek " itu bukan "perilaku objek normal", itu referensi semantik . Dan berkaitan sekali tidak dengan C struct, kompatibilitas, atau non-sense lain imam setiap OOP acak kepada Anda.
curiousguy
4
@curiousguy Amin, kakak. Sangat menyedihkan melihat seberapa sering C ++ dihancurkan karena tidak menjadi Java, ketika nilai semantik adalah salah satu hal yang membuat C ++ sangat kuat.
fgp
Ini bukan fitur, bukan kekhasan / kesalahpahaman. Ini adalah perilaku menyalin-on-stack yang normal, karena memanggil fungsi dengan arg atau (sama) mengalokasikan variabel tipe stack Baseharus 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
Croll
Jelas merupakan kesalahan C ++. Menetapkan objek turunan ke objek dasar harus dilarang, sementara mengikat objek turunan ke referensi atau pointer dari kelas dasar harus OK.
John Z. Li
7

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.

Steve Steiner
sumber
3
" Anda juga dapat menandai konstruktor salinan pada basis eksplisit " yang tidak membantu sama sekali.
curiousguy
6

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.

haberdar
sumber
7
Saya mengerti bagian "mengiris", tetapi saya tidak mengerti "masalah". Bagaimana masalah bahwa beberapa keadaan dogitu bukan bagian dari kelas Pet( breedanggota data) tidak disalin dalam variabel pet? Kode hanya tertarik pada Petanggota data - rupanya. Mengiris jelas merupakan "masalah" jika tidak diinginkan, tetapi saya tidak melihatnya di sini.
curiousguy
4
" ((Dog *)ptrP)" Saya sarankan menggunakanstatic_cast<Dog*>(ptrP)
curiousguy
Saya sarankan menunjukkan bahwa Anda akan membuat string 'berkembang biak' akhirnya membocorkan memori tanpa destruktor virtual (destruktor 'string' tidak akan dipanggil) ketika menghapus melalui 'ptrP' ... Mengapa apa yang Anda tunjukkan bermasalah? Cara mengatasinya sebagian besar desain kelas yang tepat. Masalah dalam hal ini adalah bahwa menuliskan konstruktor untuk mengontrol visibilitas ketika mewarisi itu membosankan dan mudah dilupakan. Anda tidak akan mendapatkan mendekati zona bahaya dengan kode Anda karena tidak ada polimorfisme yang terlibat atau bahkan disebutkan (mengiris akan memotong objek Anda tetapi tidak membuat program Anda macet, di sini).
Bung
24
-1 Ini sepenuhnya gagal menjelaskan masalah yang sebenarnya. C ++ memiliki nilai semantik, bukan referensi semantik seperti Java, jadi ini semua sepenuhnya yang diharapkan. Dan "memperbaiki" benar-benar adalah contoh kode C ++ yang benar - benar mengerikan . "Memperbaiki" masalah yang tidak ada seperti pengirisan jenis ini dengan beralih ke alokasi dinamis adalah resep untuk kode kereta, memori yang bocor dan kinerja yang mengerikan. Perhatikan bahwa ada beberapa kasus di mana irisan buruk, tetapi jawaban ini gagal menunjukkannya. Petunjuk: masalah dimulai jika Anda menetapkan melalui referensi .
fgp
Apakah Anda bahkan mengerti bahwa mencoba mengakses anggota tipe yang tidak didefinisikan ( Dog::breed) tidak mungkin ERROR terkait dengan SLICING?
Croll
4

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.

Minok
sumber
3
Tapi ingat, Minok, bahwa Anda TIDAK menyampaikan referensi objek itu. Anda melewati salinan BARU dari objek itu, tetapi menggunakan kelas dasar untuk menyalinnya dalam proses.
Arafangion
salinan / tugas yang dilindungi pada kelas dasar dan masalah ini diselesaikan.
Bung
1
Kamu benar. Praktik yang baik adalah dengan menggunakan kelas dasar abstrak atau untuk membatasi akses ke copy / tugas. Namun, tidak begitu mudah dikenali begitu ada di sana dan mudah dilupakan. Memanggil metode virtual dengan irisan * ini dapat membuat hal-hal misterius terjadi jika Anda lolos tanpa pelanggaran akses.
Bung
1
Saya ingat dari kursus pemrograman C ++ di universitas bahwa ada praktik terbaik yang berlaku untuk setiap kelas yang kami buat, kami diminta untuk menulis konstruktor default, menyalin konstruktor dan operator penugasan, serta destruktor. Dengan cara ini Anda memastikan bahwa copy konstruksi dan sejenisnya terjadi seperti yang Anda inginkan, saat menulis kelas ... daripada nanti beberapa perilaku aneh muncul.
Minok
3

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:

  • Kelas menyediakan tugas (secara tidak sengaja, kemungkinan dihasilkan oleh kompiler) pada kelas dasar polimorfik.
  • Klien menyalin dan mengiris contoh kelas turunan.
  • Klien memanggil fungsi anggota virtual yang mengakses status irisan.
Kawan
sumber
3

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.

Santosh
sumber
1
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; 
}
quidkid
sumber
4
Maukah Anda memberikan beberapa detail tambahan? Bagaimana jawaban Anda berbeda dari yang sudah diposting?
Alexis Pigeon
2
Saya kira lebih banyak penjelasan tidak akan buruk.
looper
-1

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
}
Varun Kumar
sumber
-1

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'
Ghulam Moinul Quadir
sumber
Diturunkan karena itu bukan contoh yang baik. Ini tidak akan berhasil jika alih-alih menyalin d ke b, Anda akan menggunakan pointer di mana d dan e masih ada tetapi Base tidak memiliki anggota tersebut. Contoh Anda hanya menunjukkan bahwa Anda tidak dapat mengakses anggota yang tidak dimiliki kelas.
Stefan Fabian
-2

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 Actionmemiliki beberapa variabel anggota. Untuk kesederhanaan, kita hanya memiliki 2, menjadi a std::string namedan a std::function<void()> f. Kemudian memiliki void activate()yang hanya mengeksekusi fanggota.

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 Actionobjek. Untuk alasan apa pun. Itu juga bisa diselesaikan dengan tangkapan lambda. Contoh ini tidak diambil 1-1 dari kode.

Jadi pria itu berasal dari Actionuntuk menambahkan rasa sendiri.
Dia melewati sebuah instance dari kelas buatannya ke rumah push_backtetapi kemudian program menjadi berantakan.

Jadi apa yang terjadi?
Seperti yang mungkin sudah Anda duga: objek telah diiris.

Informasi tambahan dari instance telah hilang, dan fsekarang rentan terhadap perilaku yang tidak terdefinisi.


Saya harap contoh ini membawa cahaya bagi orang-orang yang tidak bisa membayangkan hal-hal ketika berbicara tentang Adan Bditurunkan dalam beberapa cara.

Martin B.
sumber