C ++, ortak bir ortak ata ile birden fazla mirasla nasıl başa çıkıyor?


13

Ben bir C ++ adamı değilim, ama bunu düşünmek zorundayım. Neden C ++ 'da çoklu kalıtım mümkündür, ancak C #? ( Elmas problemini biliyorum , ama burada sorduğum şey bu değil). C ++, birden çok temel sınıftan miras alınan özdeş yöntem imzalarının belirsizliğini nasıl çözer? Ve aynı tasarım neden C # ile birleştirilmemiş?


3
Tamlık uğruna, lütfen "elmas problemi" nedir?
jcolebrand

1
@jcolebrand en.wikipedia.org/wiki/Multiple_inheritance bkz. Elmas sorunu
l46kok

1
Tabii, Google'ı aradım, ancak Sandeep'in ne anlama geldiğini nasıl bilebilirim? Belirsiz bir adla bir şeye başvuracaksanız, "Paylaşılan ortak bir ata ile çoklu miras" daha doğrudan olduğunda ...
jcolebrand

1
@jcolebrand Sorudan aldığım şeyi yansıtacak şekilde düzenledim. Bağlamdan wikipedia'da atıfta bulunulan elmas sorunu anlamına geldiğini düşünüyorum. Bir dahaki sefere dostça yorum bırakın, VE parlak yeni önerilen düzenleme aracını kullanın :)
Earlz

1
@Earlz sadece referansı anladığımda çalışır. Aksi takdirde kötü bir düzenleme yapıyorum ve bu sadece kötü juju.
jcolebrand

Yanıtlar:


24

Neden C ++ 'da çoklu kalıtım mümkündür, ancak C #?

(Sert referans olmadan), Java'da dili öğrenmeyi kolaylaştırmak için dilin ifade edilebilirliğini sınırlamak istediklerini ve çoklu miras kullanan kodun kendi iyiliği için çok daha karmaşık olduğunu düşünüyorum. Ve tam çoklu kalıtımın uygulanması çok daha karmaşık olduğundan, sanal makineyi de çok basitleştirdi (çoklu kalıtım özellikle çöp toplayıcı ile kötü etkileşime giriyor, çünkü işaretçileri nesnenin ortasına (tabanın başında) tutmayı gerektiriyor )

Ve C # tasarlarken sanırım Java'ya baktılar, tam çoklu mirasın çok fazla özlenmediğini gördüler ve işleri basitleştirmek için seçildiler.

C ++, birden çok temel sınıftan miras alınan özdeş yöntem imzalarının belirsizliğini nasıl çözer?

Öyle değil . Temel sınıf yöntemini belirli bir tabandan açıkça çağırmak için bir sözdizimi vardır, ancak sanal yöntemlerden yalnızca birini geçersiz kılmanın bir yolu yoktur ve alt sınıftaki yöntemi geçersiz kılmazsanız, tabanı belirtmeden çağırmak mümkün değildir. sınıf.

Ve aynı tasarım neden C # ile birleştirilmemiş?

Dahil edilecek hiçbir şey yok.


Giorgio, yorumlarda arayüz genişletme yöntemlerinden bahsettiğinden, karışımların ne olduğunu ve çeşitli dillerde nasıl uygulandığını açıklayacağım.

Java ve C # arabirimleri yalnızca bildirme yöntemleriyle sınırlıdır. Ancak, arabirimi devralan her sınıfta yöntemlerin uygulanması gerekir. Bununla birlikte, bazı yöntemlerin diğerlerine göre varsayılan uygulamalarının sağlanmasının yararlı olacağı geniş bir arabirim sınıfı vardır. Yaygın örnek karşılaştırılabilir (sözde dilde):

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; }
};

Tam sınıftan farkı, bunun herhangi bir veri üyesi içerememesidir. Bunu uygulamak için birkaç seçenek vardır. Açıkçası çoklu kalıtım birdir. Ancak çoklu mirasın uygulanması oldukça karmaşıktır. Ama burada gerçekten gerekli değil. Bunun yerine, birçok dil, mixin'i sınıf tarafından uygulanan bir arabirime bölerek uygular ve yöntem uygulamalarının bir deposu, ya sınıfın kendisine enjekte edilir ya da bir ara taban sınıfı oluşturulur ve oraya yerleştirilir. Bu Ruby ve D' de uygulanır, Java 8'de uygulanır ve merakla yinelenen şablon deseni kullanılarak C ++ ile manuel olarak uygulanabilir . Yukarıdaki, CRTP formunda, şuna benzer:

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(); }
    ...
};

ve aşağıdaki gibi kullanılır:

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

Bu, normal temel sınıfın yaptığı gibi sanal olarak bildirilmeyi gerektirmez, bu nedenle arayüz şablonlarda kullanılırsa yararlı optimizasyon seçeneklerini açık bırakır. C ++ 'da bunun muhtemelen ikinci ebeveyn olarak devralınacağını, ancak birden fazla kalıtıma izin vermeyen dillerde tek bir kalıtım zincirine eklendiğini unutmayın.

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

Derleyici uygulaması sanal dağıtımı önleyebilir veya önleyemez.

C # 'da farklı bir uygulama seçildi. C # 'da uygulamalar tamamen ayrı sınıfın statik yöntemleridir ve belirli bir ad yöntemi mevcut değilse, ancak bir "uzatma yöntemi" tanımlanmışsa, yöntem çağrısı sözdizimi derleyici tarafından uygun şekilde yorumlanır. Bunun avantajı, derlenmiş sınıfa genişletme yöntemlerinin eklenmesi ve bu tür yöntemlerin geçersiz kılınamaması gibi dezavantajın, örneğin optimize edilmiş versiyonun sağlanması için vardır.


Arayüz uzatma yöntemleri ile Java 8'e çoklu kalıtımın dahil edileceğinden bahsetmek isteyebilirsiniz.
Giorgio

@Giorgio: Hayır, Java'da kesinlikle birden fazla miras tanıtılmayacak. Mixins, çok farklı bir şey olacaktır, ancak çoklu miras kullanmak için kalan birçok nedeni ve merakla tekrarlanan şablon desenini (CRTP) kullanmak için çoğu nedeni kapsar ve çoğunlukla çoklu kalıtım gibi değil, çoğunlukla CRTP gibi çalışır.
Jan Hudec

Bence çoklu kalıtım bir nesnenin ortasına işaretçiler gerektirmez . Eğer öyleyse, çoklu arayüz kalıtımı da gerektirir.
svick

@svick: Hayır, değil. Ancak alternatif, üye erişimi için sanal dağıtım gerektirdiğinden çok daha az verimlidir.
Jan Hudec

Benimkinden çok daha eksiksiz bir cevap için +1.
Nathan C.Tresch

2

Yanıt, ad alanı çakışmaları durumunda C ++ 'da düzgün çalışmadığıdır. Bkz bu . İsim alanının çatışmasını önlemek için işaretçilerle her türlü dönmeyi yapmanız gerekir. MS'de Visual Studio ekibinde çalıştım ve en azından kısmen heyet geliştirmelerinin nedeninin ad alanı çarpışmasını tamamen önlemekti. Önceden Arayüzleri de çoklu kalıtım çözümünün bir parçası olarak gördüklerini söylemiştim ama yanılmışım. Arayüzler gerçekten şaşırtıcı ve C ++, FWIW'da çalışmak için yapılabilir.

Temsilci özellikle ad alanı çarpışmasını ele alır: 5 sınıfa temsilci atayabilirsiniz ve bunların 5'i yöntemlerini birinci sınıf üyesi olarak kendi kapsamınıza dışa aktarır. Dışarıdan bakıldığında bu IS çoklu miras.


1
Bu, MI'yı C #'a dahil etmemenin birincil nedeni olduğunu pek olası görmüyorum. Ayrıca, bu yazı, "ad alanı çakışmaları" olmadığında MI neden C ++ 'da çalışıyor sorusuna cevap vermez.
Doc Brown

@DocBrown MS'de Visual Studio ekibinde çalıştım ve ad alanı çarpışmasını tamamen önlemek için en azından kısmen temsilci seçme ve arabirim geliştirmelerinin nedeninin sizi temin ederim. Sorunun kalitesine gelince, meh. Sizin oyunuzu kullanın, diğerleri bunun yararlı olduğunu düşünüyor gibi görünüyor.
Nathan C.Tresch

1
Doğru olduğunu düşündüğüm için cevabınızı küçümsemeye niyetim yok. Ama cevabınız MI'ın C ++ 'da tamamen kullanılamaz olduğunu iddia ediyor ve bu yüzden C #' da tanıtmamışlar. C ++ 'da MI'yı çok sevmeme rağmen, tamamen kullanılamaz olmadığını düşünüyorum .
Doc Brown

1
Bunun nedenlerinden biri ve hiç işe yaramadığını ima etmek istemedim. Hesaplamada bir şey deterministik olmadığında, bunun "kırık" olduğunu veya "vudu gibi olduğunu düşündüğümde" işe yaramadığını "söyleyebilirim. : D
Nathan C.Tresch

1
"isim-alanı çarpışmaları" belirleyici değil mi?
Doc Brown
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.