Bagaimana C ++ menangani multiple inheritance dengan leluhur bersama?

13

Saya bukan orang C ++, tapi saya terpaksa memikirkan hal ini. Mengapa multiple inheritance dimungkinkan di C ++, tetapi tidak di C #? (Saya tahu masalah berlian , tapi bukan itu yang saya tanyakan di sini). Bagaimana C ++ menyelesaikan ambiguitas tanda tangan metode identik yang diwarisi dari beberapa kelas dasar? Dan mengapa desain yang sama tidak dimasukkan ke dalam C #?

Sandeep
sumber
3
Demi kelengkapan, apa "masalah intan" itu?
jcolebrand
1
@jcolebrand en.wikipedia.org/wiki/Multiple_inheritance lihat masalah Diamond
l46kok
1
Tentu, saya meng-google-nya, tetapi bagaimana saya tahu bahwa itulah yang dimaksud Sandeep? Jika Anda akan merujuk sesuatu dengan nama yang tidak jelas, ketika "Warisan berganda dengan leluhur bersama yang sama" lebih langsung ...
jcolebrand
1
@ jcolebrand Saya mengeditnya untuk mencerminkan apa yang saya dapatkan dari pertanyaan. Saya menganggap maksudnya masalah intan dirujuk pada wikipedia dari konteksnya. Lain kali lepaskan komentar bersahabat, DAN gunakan alat edit yang disarankan dan baru :)
Earlz
1
@ Earlz yang hanya berfungsi ketika saya mengerti referensi. Kalau tidak, saya membuat edit yang buruk, dan itu hanya juju yang buruk.
jcolebrand

Jawaban:

24

Mengapa multiple inheritance dimungkinkan di C ++, tetapi tidak di C #?

Saya pikir (tanpa referensi keras), bahwa di Jawa mereka ingin membatasi ekspresi bahasa untuk membuat bahasa lebih mudah dipelajari dan karena kode yang menggunakan banyak pewarisan lebih sering terlalu rumit untuk kebaikannya sendiri daripada tidak. Dan karena multiple multiple inheritance jauh lebih rumit untuk diimplementasikan, sehingga ia menyederhanakan banyak mesin virtual (multiple inheritance berinteraksi secara buruk dengan pengumpul sampah, karena itu membutuhkan menjaga pointer ke tengah objek (di awal pangkalan) )

Dan ketika mendesain C # saya pikir mereka melihat Java, melihat bahwa multiple inheritance penuh memang tidak ketinggalan dan dipilih untuk menjaga hal-hal yang sederhana juga.

Bagaimana C ++ menyelesaikan ambiguitas tanda tangan metode identik yang diwarisi dari beberapa kelas dasar?

Itu tidak tidak . Ada sintaks untuk memanggil metode kelas dasar dari basis spesifik secara eksplisit, tetapi tidak ada cara untuk menimpa hanya satu dari metode virtual dan jika Anda tidak menimpa metode dalam subkelas, tidak mungkin untuk memanggilnya tanpa menentukan basis kelas.

Dan mengapa desain yang sama tidak dimasukkan ke dalam C #?

Tidak ada yang perlu digabungkan.


Karena Giorgio menyebutkan metode ekstensi antarmuka dalam komentar, saya akan menjelaskan apa itu mixin dan bagaimana mereka diimplementasikan dalam berbagai bahasa.

Antarmuka di Java dan C # terbatas hanya untuk mendeklarasikan metode. Tetapi metode harus diterapkan di setiap kelas yang mewarisi antarmuka. Namun ada kelas besar antarmuka, di mana akan berguna untuk memberikan implementasi standar dari beberapa metode dalam hal yang lain. Contoh umum dapat dibandingkan (dalam bahasa pseudo):

mixin IComparable {
    public bool operator<(IComparable r) = 0;
    public bool operator>(IComparable r) { return r < this; }
    public bool operator<=(IComparable r) { return !(r < this); }
    public bool operator>=(IComparable r) { return !(r > this); }
    public bool operator==(IComparable r) { return !(r < this) && !(r > this); }
    public bool operator!=(IComparable r) { return r < this || r > this; }
};

Perbedaan dari kelas penuh adalah ini tidak dapat berisi data anggota. Ada beberapa opsi untuk mengimplementasikan ini. Jelas banyak warisan adalah satu. Tetapi multiple inheritance agak rumit untuk diimplementasikan. Tapi itu tidak benar-benar dibutuhkan di sini. Sebagai gantinya, banyak bahasa mengimplementasikan ini dengan memisahkan mixin dalam sebuah antarmuka, yang diimplementasikan oleh kelas dan repositori implementasi metode, yang disuntikkan ke dalam kelas itu sendiri atau kelas dasar menengah dihasilkan dan mereka ditempatkan di sana. Ini diimplementasikan di Ruby dan D , akan diimplementasikan di Java 8 dan dapat diimplementasikan secara manual di C ++ menggunakan pola templat yang anehnya berulang . Di atas, dalam bentuk CRTP, terlihat seperti:

template <typename Derived>
class IComparable {
    const Derived &_d() const { return static_cast<const Derived &>(*this); }
public:
    bool operator>(const IComparable &r) const { r._d() < _d(); }
    bool operator<=(const IComparable &r) const { !(r._d() < _d(); }
    ...
};

dan digunakan seperti:

class Concrete : public IComparable<Concrete> { ... };

Ini tidak memerlukan apa pun untuk dinyatakan virtual seperti yang akan dilakukan oleh kelas dasar biasa, jadi jika antarmuka yang digunakan dalam templat membiarkan opsi pengoptimalan yang bermanfaat terbuka. Perhatikan, bahwa dalam C ++ ini mungkin masih akan diwariskan sebagai induk kedua, tetapi dalam bahasa yang tidak memungkinkan banyak pewarisan itu dimasukkan ke dalam rantai pewarisan tunggal, jadi lebih seperti

template <typename Derived, typename Base>
class IComparable : public Base { ... };
class Concrete : public IComparable<Concrete, Base> { ... };

Implementasi kompiler mungkin atau mungkin tidak menghindari pengiriman virtual.

Implementasi yang berbeda dipilih dalam C #. Dalam C # implementasinya adalah metode statis dari kelas yang benar-benar terpisah dan sintaks pemanggilan metode secara tepat ditafsirkan oleh kompiler jika metode nama yang diberikan tidak ada, tetapi "metode ekstensi" didefinisikan. Ini memiliki keuntungan bahwa metode ekstensi dapat ditambahkan ke kelas yang sudah dikompilasi dan kelemahan bahwa metode tersebut tidak dapat ditimpa misalnya untuk menyediakan versi yang dioptimalkan.

Jan Hudec
sumber
Orang mungkin ingin menyebutkan bahwa pewarisan berganda akan diperkenalkan ke Java 8 dengan metode ekstensi antarmuka.
Giorgio
@Iorgio: Tidak, multiple inheritance tidak akan diperkenalkan di Jawa. Mixin akan menjadi, yang merupakan hal yang sangat berbeda, meskipun mencakup banyak alasan yang tersisa untuk menggunakan pewarisan berganda dan sebagian besar alasan untuk menggunakan pola template berulang yang aneh (CRTP) dan bekerja sebagian besar seperti CRTP, tidak seperti banyak pewarisan sama sekali.
Jan Hudec
Saya pikir multiple inheritance tidak memerlukan pointer ke tengah suatu objek. Jika ya, pewarisan banyak antarmuka juga akan memerlukannya.
svick
@vick: Tidak, tidak. Tetapi alternatif ini jauh lebih efisien karena memerlukan pengiriman virtual untuk akses anggota.
Jan Hudec
+1 untuk jawaban yang jauh lebih lengkap daripada saya.
Nathan C. Tresch
2

Jawabannya adalah bahwa itu tidak berfungsi dengan benar di C ++ jika terjadi bentrokan namespace. Lihat ini . Untuk menghindari bentrok namespace, Anda harus melakukan semua jenis rotasi dengan pointer. Saya bekerja di MS di tim Visual Studio dan saya setidaknya sebagian alasan mereka mengembangkan delegasi adalah untuk menghindari tabrakan namespace sama sekali. Sebelumnya saya telah mengatakan bahwa mereka juga menganggap Antarmuka sebagai bagian dari solusi pewarisan berganda, tetapi saya salah. Antarmuka benar-benar luar biasa dan dapat dibuat untuk bekerja di C ++, FWIW.

Delegasi secara khusus membahas tabrakan namespace: Anda dapat mendelegasikan ke 5 kelas dan semuanya akan mengekspor metode mereka ke ruang lingkup Anda sebagai anggota kelas satu. Di luar mencari di IS ini warisan ganda.

Nathan C. Tresch
sumber
1
Saya merasa sangat tidak mungkin bahwa ini adalah alasan utama untuk tidak memasukkan MI dalam C #. Lebih jauh, posting ini tidak menjawab pertanyaan mengapa MI bekerja di C ++ ketika Anda tidak memiliki "bentrok namespace".
Doc Brown
@DocBrown Saya bekerja di MS di tim Visual Studio dan saya jamin itu setidaknya sebagian alasan mereka mengembangkan delegasi dan antarmuka, untuk menghindari tabrakan namespace sama sekali. Mengenai kualitas pertanyaan, meh. Gunakan downvote Anda, orang lain tampaknya berpikir itu berguna.
Nathan C. Tresch
1
Saya tidak punya niat untuk menurunkan jawaban Anda karena saya pikir itu jawaban yang benar. Tetapi jawaban Anda berpura-pura MI benar-benar tidak dapat digunakan dalam C ++, dan itulah alasan mereka tidak memperkenalkannya dalam C #. Meskipun saya tidak terlalu menyukai MI di C ++, saya pikir itu tidak sepenuhnya tidak dapat digabungkan.
Doc Brown
1
Itu salah satu alasannya, dan saya tidak bermaksud mengatakan bahwa itu tidak pernah berhasil. Ketika ada sesuatu yang non-deterministik dalam komputasi saya cenderung mengatakan itu "rusak", atau bahwa itu "tidak berfungsi" ketika saya bermaksud mengatakan "itu seperti voodoo, itu mungkin atau mungkin tidak bekerja, nyalakan lilin Anda dan berdoa." : D
Nathan C. Tresch
1
Apakah "namespace collisions" tidak menentukan?
Doc Brown