class A { public: void eat(){ cout<<"A";} };
class B: virtual public A { public: void eat(){ cout<<"B";} };
class C: virtual public A { public: void eat(){ cout<<"C";} };
class D: public B,C { public: void eat(){ cout<<"D";} };
int main(){
A *a = new D();
a->eat();
}
Saya memahami masalah berlian, dan potongan kode di atas tidak memiliki masalah itu.
Bagaimana sebenarnya warisan virtual menyelesaikan masalah?
Apa yang saya pahami:
Ketika saya katakan A *a = new D();
, kompilator ingin tahu apakah suatu objek bertipe D
dapat ditugaskan ke penunjuk tipe A
, tetapi ia memiliki dua jalur yang dapat diikuti, tetapi tidak dapat memutuskan sendiri.
Jadi, bagaimana warisan virtual menyelesaikan masalah (membantu penyusun mengambil keputusan)?
B
's orC
' s implementasi sebagai gantinya? Terima kasih!Instance dari kelas turunan menyimpan anggota dari kelas dasarnya.
Tanpa warisan virtual, tata letak memori akan terlihat seperti (perhatikan dua salinan
A
anggota di kelasD
):Dengan warisan virtual, tata letak memori terlihat seperti (perhatikan salinan tunggal
A
anggota di kelasD
):Untuk setiap kelas turunan, compiler membuat tabel virtual yang menampung pointer ke anggota kelas basis virtualnya yang disimpan di kelas turunan, dan menambahkan pointer ke tabel virtual tersebut di kelas turunan.
sumber
Mengapa jawaban lain?
Nah, banyak posting di SO dan artikel di luar mengatakan, bahwa masalah berlian diselesaikan dengan membuat satu instance,
A
bukan dua (satu untuk setiap indukD
), sehingga menyelesaikan ambiguitas. Namun, ini tidak memberi saya pemahaman yang komprehensif tentang proses, saya berakhir dengan lebih banyak pertanyaan sepertiB
danC
mencoba untuk membuat contoh yang berbedaA
misalnya memanggil konstruktor parametrized dengan parameter berbeda (D::D(int x, int y): C(x), B(y) {}
)? Contoh mana yangA
akan dipilih untuk menjadi bagianD
?B
, tetapi untuk warisan virtualC
? Apakah cukup untuk membuat satu contohA
dalamD
?Tidak dapat memprediksi perilaku tanpa mencoba contoh kode berarti tidak memahami konsepnya. Di bawah ini adalah apa yang membantu saya memahami warisan virtual.
Ganda A
Pertama, mari kita mulai dengan kode ini tanpa warisan virtual:
Mari kita lihat keluaran. Menjalankan
B b(2);
pembuatanA(2)
seperti yang diharapkan, sama untukC c(3);
:D d(2, 3);
kebutuhan baikB
danC
, masing-masing dari mereka menciptakan sendiriA
, jadi kita harus gandaA
did
:Itulah alasan untuk
d.getX()
menyebabkan kesalahan kompilasi karena kompiler tidak dapat memilihA
instance mana yang harus dipanggil metode. Masih mungkin untuk memanggil metode secara langsung untuk kelas induk yang dipilih:Virtualitas
Sekarang mari tambahkan warisan virtual. Menggunakan sampel kode yang sama dengan perubahan berikut:
Mari melompat ke pembuatan
d
:Anda dapat melihat,
A
dibuat dengan konstruktor default mengabaikan parameter yang diteruskan dari konstruktorB
danC
. Saat ambiguitas hilang, semua panggilan untukgetX()
mengembalikan nilai yang sama:Tetapi bagaimana jika kita ingin memanggil konstruktor parametrized
A
? Ini dapat dilakukan dengan memanggilnya secara eksplisit dari konstruktorD
:Biasanya, kelas dapat secara eksplisit menggunakan konstruktor hanya dari orang tua langsung, tetapi ada pengecualian untuk kasus warisan virtual. Menemukan aturan ini "diklik" untuk saya dan sangat membantu memahami antarmuka virtual:
Kode
class B: virtual A
berarti, setiap kelas yang diwarisi dariB
sekarang bertanggung jawab untuk membuatA
sendiri, karenaB
tidak akan melakukannya secara otomatis.Dengan mengingat pernyataan ini, mudah untuk menjawab semua pertanyaan yang saya miliki:
D
pembuatan tidakB
jugaC
bertanggung jawab atas parameterA
, itu sepenuhnya terserahD
saja.C
akan mendelegasikan pembuatanA
keD
, tetapiB
akan membuat instance sendiriA
sehingga mengembalikan masalah berliansumber
virtual
kata kunci sebagai "ditentukan nanti (dalam subclass)", artinya tidak "benar-benar" ditentukan tetapi "secara virtual". Interpretasi ini tidak hanya berfungsi untuk kelas dasar tetapi juga untuk metode. Terima kasih!Masalahnya bukanlah path yang harus diikuti oleh compiler. Masalahnya adalah titik akhirnya dari jalan itu: hasil dari para pemain. Dalam hal konversi jenis, jalur tidak menjadi masalah, hanya hasil akhirnya yang menentukan.
Jika Anda menggunakan pewarisan biasa, setiap jalur memiliki titik akhir tersendiri, yang berarti bahwa hasil pemerannya ambigu, itulah masalahnya.
Jika Anda menggunakan warisan virtual, Anda mendapatkan hierarki berbentuk berlian: kedua jalur mengarah ke titik akhir yang sama. Dalam hal ini masalah pemilihan jalan sudah tidak ada lagi (atau, lebih tepatnya, tidak penting lagi), karena kedua jalan tersebut mengarah pada hasil yang sama. Hasilnya tidak lagi ambigu - itulah yang penting. Jalan yang tepat tidak.
sumber
Sebenarnya contohnya harus sebagai berikut:
... dengan begitu hasilnya akan menjadi benar: "EAT => D"
Warisan virtual hanya menyelesaikan duplikasi kakek! TAPI Anda masih perlu menentukan metode menjadi virtual untuk mendapatkan metode diganti dengan benar ...
sumber