Çoklu kalıtımla ilgili kesin sorun nedir?


121

İnsanların sürekli olarak C # veya Java'nın bir sonraki sürümüne çoklu mirasın dahil edilip edilmeyeceğini sorduğunu görebiliyorum. Bu yeteneğe sahip olacak kadar şanslı olan C ++ arkadaşları, bunun birisine sonunda kendilerini asması için bir ip vermek gibi olduğunu söylüyorlar.

Çoklu mirasın sorunu nedir? Somut örnekler var mı?


54
C ++ 'nın size kendinizi asmanıza yetecek kadar ip vermek için harika olduğunu belirtmek isterim.
tloach

1
Aynı problemlerin çoğunu ele alan (ve IMHO çözen) çoklu mirasa bir alternatif için, Özellikler'e bakın ( iam.unibe.ch/~scg/Research/Traits )
Bevan

52
C ++ 'nın size kendinizi ayağınızdan vurmaya yetecek kadar ip verdiğini düşündüm.
KeithB

6
Bu soru, genel olarak MI ile ilgili bir sorun olduğunu varsayıyor gibi görünüyor, oysa ME'nin gündelik kullanımda olduğu birçok dil buldum. Kesinlikle belirli dillerin MI'yı ele almasıyla ilgili sorunlar var, ancak genel olarak MI'nın önemli sorunları olduğunun farkında değilim.
David Thornley

Yanıtlar:


86

En belirgin sorun, işlevin geçersiz kılınmasıdır.

Diyelim ki iki sınıfımız var Ave Bher ikisi de bir yöntemi tanımlıyor doSomething. Şimdi üçüncü bir sınıfını tanımlamak Chem, devralır Ave Bfakat geçersiz kılmaz doSomethingyöntemi.

Derleyici bu kodu tohumladığında ...

C c = new C();
c.doSomething();

... yöntemin hangi uygulamasını kullanmalıdır? Daha fazla açıklama yapılmadan derleyicinin belirsizliği çözmesi imkansızdır.

Geçersiz kılmanın yanı sıra, çoklu kalıtımla ilgili diğer büyük sorun, fiziksel nesnelerin bellekteki düzenidir.

C ++, Java ve C # gibi diller, her nesne türü için sabit bir adres tabanlı düzen oluşturur. Bunun gibi bir şey:

class A:
    at offset 0 ... "abc" ... 4 byte int field
    at offset 4 ... "xyz" ... 8 byte double field
    at offset 12 ... "speak" ... 4 byte function pointer

class B:
    at offset 0 ... "foo" ... 2 byte short field
    at offset 2 ... 2 bytes of alignment padding
    at offset 4 ... "bar" ... 4 byte array pointer
    at offset 8 ... "baz" ... 4 byte function pointer

Derleyici makine kodu (veya bayt kodu) ürettiğinde, her yönteme veya alana erişmek için bu sayısal uzaklıkları kullanır.

Çoklu miras, işi çok zorlaştırır.

Sınıf ise Chem devralır Ave B, derleyici veri düzeni karar vermek vardır ABsırayla veya BAsırayla.

Ama şimdi bir Bnesnede yöntemleri çağırdığınızı hayal edin . Gerçekten sadece bir Bmi? Yoksa arayüzü Caracılığıyla polimorfik olarak adlandırılan bir nesne Bmi? Nesnenin gerçek kimliğine bağlı olarak, fiziksel düzen farklı olacaktır ve çağrı yerinde çalıştırılacak işlevin ofsetini bilmek imkansızdır.

Bu tür bir sistemi ele almanın yolu, sabit yerleşim yaklaşımından vazgeçmek, her nesnenin işlevleri çağırmadan veya alanlarına erişmeden önce düzeni için sorgulanmasına izin vermektir .

Yani ... uzun lafın kısası ... derleyici yazarların çoklu mirası desteklemesi baş belasıdır. Yani Guido van Rossum gibi biri python tasarladığında veya Anders Hejlsberg c # tasarladığında, çoklu mirası desteklemenin derleyici uygulamalarını önemli ölçüde daha karmaşık hale getireceğini biliyorlar ve muhtemelen faydanın maliyete değdiğini düşünmüyorlar.


62
Ehm, Python MI'yı destekliyor
Nemanja Trifunovic

26
Bunlar pek ikna edici argümanlar değil - sabit düzen meselesi çoğu dilde hiç de zor değil; C ++ 'da zor çünkü bellek opak değildir ve bu nedenle işaretçi aritmetik varsayımlarında bazı zorluklarla karşılaşabilirsiniz. Sınıf tanımlarının statik olduğu dillerde (java, C # ve C ++ 'da olduğu gibi), çoklu miras adı çatışmaları derleme zamanı yasaklanabilir (ve C # bunu her halükarda arabirimlerle yapar!).
Eamon Nerbonne

10
OP sadece sorunları anlamak istiyordu ve ben bunları kişisel olarak editörlük yapmadan açıkladım. Sadece dil tasarımcılarının ve derleyici uygulayıcılarının "muhtemelen faydanın maliyete değeceğini düşünmediğini" söyledim.
benjismith

12
" En bariz sorun, işlevin geçersiz kılınmasıdır. " Bunun işlev geçersiz kılma ile ilgisi yoktur. Bu basit bir belirsizlik problemidir.
wonderguy

10
Python MI desteklediğinden, bu yanıt Guido ve Python hakkında bazı yanlış bilgiler içermektedir. "Kalıtımı destekleyeceğim sürece, çoklu kalıtımın basit fikirli bir versiyonunu da destekleyebileceğime karar verdim." - Guido van Rossum python-history.blogspot.com/2009/02/… - Artı, belirsizlik çözümlemesi derleyicilerde oldukça yaygındır (değişkenler engellenecek yerel, işleve yerel, çevreleyen işlevlere yerel, nesne üyeleri, sınıf üyeleri, Küresel, vb.), ekstra bir kapsamın nasıl bir fark yaratacağını anlamıyorum.
marcus

46

Bahsettiğiniz sorunları çözmek o kadar da zor değil. Aslında, örneğin Eiffel bunu çok iyi yapıyor! (ve keyfi seçimler ya da herhangi bir şey yapmadan)

Örneğin, her ikisi de foo () yöntemine sahip A ve B'den miras alırsanız, o zaman elbette C sınıfınızda hem A hem de B'den miras alan keyfi bir seçim istemezsiniz. c.foo () çağrılırsa veya C'deki yöntemlerden birini yeniden adlandırmanız gerekirse kullanılır (bar () olabilir)

Ayrıca çoklu kalıtımın genellikle oldukça yararlı olduğunu düşünüyorum. Eyfel kütüphanelerine bakarsanız, her yerde kullanıldığını görürsünüz ve kişisel olarak Java'da programlamaya geri dönmem gerektiğinde bu özelliği kaçırdım.


26
Katılıyorum. İnsanların MI'dan nefret etmelerinin ana nedeni JavaScript'le veya statik yazımla aynıdır: çoğu insan onu yalnızca çok kötü uygulamalarını kullanmış veya çok kötü kullanmıştır. MI'yı C ++ ile değerlendirmek, OOP'yi PHP tarafından değerlendirmek veya Pintos tarafından otomobilleri yargılamak gibidir.
Jörg W Mittag

2
@curiousguy: MI, C ++ 'nın birçok "özelliği" gibi, endişelenecek başka bir karmaşıklık dizisi daha getiriyor. Sadece açık olması, üzerinde çalışmayı veya hata ayıklamayı kolaylaştırmaz. Konudan saptığı için bu zinciri kaldırıyorsun ve yine de mahvettin.
Guvante

4
@Guvante, herhangi bir dilde MI ile ilgili tek sorun, boktan programcıların bir öğretici okuyabileceklerini ve aniden bir dil öğrenebileceklerini düşünmeleridir.
Miles Rout

2
Dil özelliklerinin yalnızca kodlama süresini azaltmakla ilgili olmadığını iddia ediyorum. Aynı zamanda bir dilin ifade gücünü artırmak ve performansı artırmakla ilgilidir.
Miles Rout

4
Ayrıca, hatalar sadece aptallar yanlış kullandığında MI'dan kaynaklanır.
Miles Rout

27

Elmas sorunu :

iki B ve C sınıfı A'dan miras aldığında ve D sınıfı hem B hem de C'den miras aldığında ortaya çıkan bir belirsizlik A'da B ve C'nin geçersiz kıldığı ve D'nin onu geçersiz kılmadığı bir yöntem varsa, o zaman yöntem D miras alır: B'ninki mi yoksa C'ninki mi?

... Bu durumda sınıf kalıtım diyagramının şekli nedeniyle "elmas problemi" olarak adlandırılır. Bu durumda, A sınıfı üstte, hem B hem de C altında ayrı ayrı ve D, bir elmas şekli oluşturmak için altta ikisini birleştiriyor ...


4
sanal miras olarak bilinen bir çözüme sahip. Sadece yanlış yaparsan sorun olur.
Ian Goldby

1
@IanGoldby: Sanal kalıtım, bir örneğin türetildiği veya bunun yerine ikame edilebildiği tüm türler arasında kimliği koruyan yukarı ve aşağı yayınlara izin verilmesi gerekmiyorsa , sorunun bir bölümünü çözmek için bir mekanizmadır . Verilen X: B; Hasılat: B; ve Z: X, Y; someZ'nin bir Z örneği olduğunu varsayalım. Sanal kalıtımla, (B) (X) someZ ve (B) (Y) someZ, farklı nesnelerdir; ya verilen, biri downCast yoluyla diğer almak ve hava bacası, ama ne olabilir biri varsa someZve bunu atmak isteyen Objectve daha sonra B? Hangisini Balacak?
supercat

2
@supercat Belki, ancak bunun gibi sorunlar büyük ölçüde teoriktir ve her durumda derleyici tarafından belirtilebilir. Önemli olan, çözmeye çalıştığınız sorunun farkında olmak ve ardından en iyi aracı kullanmak, "neden" i anlamakla ilgilenmek istemeyen insanların dogmalarını görmezden gelmektir.
Ian Goldby

@IanGoldby: Bunun gibi sorunlar, yalnızca söz konusu tüm sınıflara aynı anda erişime sahipse derleyici tarafından bildirilebilir. Bazı çerçevelerde, bir temel sınıftaki herhangi bir değişiklik her zaman tüm türetilmiş sınıfların yeniden derlenmesini gerektirir, ancak türetilmiş sınıfları yeniden derlemek zorunda kalmadan temel sınıfların daha yeni sürümlerini kullanma yeteneği (bunlardan birinin kaynak kodu olmayabilir) yararlı bir özelliktir. bunu sağlayabilen çerçeveler için. Dahası, problemler sadece teorik değil. Object
.NET'teki

3
@IanGoldby: Yeterince adil. Demek istediğim, Java ve .NET uygulayıcılarının genelleştirilmiş MI'yı desteklememeye karar verme konusunda sadece "tembel" olmadıklarıydı; genelleştirilmiş MI'nın desteklenmesi, çerçevelerinin, geçerliliği birçok kullanıcı için MI'dan daha yararlı olan çeşitli aksiyomları desteklemesini engelleyecektir.
supercat

21

Çoklu miras, sıklıkla kullanılmayan ve kötüye kullanılabilen, ancak bazen gerekli olan şeylerden biridir.

İyi bir alternatif olmadığında kötüye kullanılabileceği için bir özellik eklemeyi asla anlamadım. Arayüzler, çoklu kalıtıma alternatif değildir. Birincisi, ön koşulları veya son koşulları uygulamanıza izin vermiyorlar. Diğer herhangi bir araç gibi, ne zaman ve nasıl kullanılacağını bilmeniz gerekir.


Neden ön ve son koşulları uygulamanıza izin vermediklerini açıklayabilir misiniz?
Yttrill

2
@Yttrill çünkü arabirimler yöntem uygulamalarına sahip olamaz. Nereye koyuyorsunuz assert?
curiousguy

1
@curiousguy: ön ve son koşulları doğrudan arayüze koymanıza olanak tanıyan uygun bir sözdizimine sahip bir dil kullanıyorsunuz: "iddiaya" gerek yok. Felix'ten örnek: fun div (num: int, den: int, den! = 0): int bekleme sonucu == 0, num == 0 anlamına gelir;
Yttrill

@Yttrill Tamam, ancak Java gibi bazı diller MI veya "ön ve son koşulları doğrudan arabirime" desteklemiyor.
curiousguy

Mevcut olmadığı için sık kullanılmaz ve onu nasıl kullanacağımızı bilmiyoruz. Bazı Scala kodlarına bakarsanız, işlerin nasıl yaygınlaşmaya başladığını ve niteliklere göre yeniden düzenlenebileceğini göreceksiniz (Tamam, bu MI değil, ama benim açımdan kanıtlıyor).
santiagobasulto

16

Diyelim ki, her ikisi de C tarafından miras alınan A ve B nesneleriniz var. A ve B'nin her ikisi de foo () uyguluyor ve C bunu yapmıyor. C.foo () diyorum. Hangi uygulama seçilir? Başka sorunlar da var ama bu tür şeyler çok büyük.


1
Ama bu gerçekten somut bir örnek değil. Hem A hem de B'nin bir işlevi varsa, C'nin de kendi uygulamasına ihtiyaç duyması muhtemeldir. Aksi takdirde, kendi foo () fonksiyonunda A :: foo () 'yu çağırabilir.
Peter Kühne

@Quantum: Ya olmazsa? Sorunu bir düzey kalıtımla görmek kolaydır, ancak çok sayıda düzeyiniz varsa ve iki kez bir yerde rastgele bir işleviniz varsa, bu çok zor bir sorun haline gelir.
tloach

Ayrıca, önemli olan, hangisini istediğinizi belirleyerek A veya B yöntemini çağıramayacağınız değil, önemli olan nokta şu ki, eğer belirtmezseniz, birini seçmenin iyi bir yolu yoktur. C ++ 'nın bunu nasıl ele aldığından emin değilim, ama eğer birisi bundan bahsedebilir mi?
tloach

2
@tloach - C belirsizliği çözmezse, derleyici bu hatayı algılayabilir ve bir derleme zamanı hatası döndürebilir.
Eamon Nerbonne

@Earmon - Polimorfizm nedeniyle, foo () sanalsa, derleyici derleme sırasında bunun bir sorun olacağını bile bilmeyebilir.
tloach

5

Çoklu kalıtımla ilgili temel sorun, tloach'ın örneğiyle güzel bir şekilde özetlenmiştir. Aynı işlevi veya alanı uygulayan birden çok temel sınıftan miras alırken, derleyicinin hangi uygulamanın devralınacağına karar vermesi gerekir.

Aynı temel sınıftan miras alan birden çok sınıftan miras aldığınızda bu daha da kötüleşir. (elmas miras, miras ağacını çizerseniz bir elmas şekli alırsınız)

Bu sorunlar, bir derleyicinin üstesinden gelmesi için gerçekten sorunlu değildir. Ancak derleyicinin burada yapması gereken seçim oldukça keyfi, bu da kodu çok daha az sezgisel hale getiriyor.

İyi bir OO tasarımı yaparken asla çoklu mirasa ihtiyacım olmadığını görüyorum. İhtiyaç duyduğum durumlarda, kalıtımı genellikle işlevselliği yeniden kullanmak için kullandığımı, kalıtım ise yalnızca "is-a" ilişkileri için uygun olduğunu görüyorum.

Aynı sorunları çözen ve çoklu kalıtımın sahip olduğu sorunları olmayan mixins gibi başka teknikler de vardır.


4
Derlenen öğenin keyfi bir seçim yapmasına gerek yoktur - yalnızca hata verebilir. C # 'da türü ([..bool..]? "test": 1)nedir?
Eamon Nerbonne

4
C ++ 'da, derleyici asla böyle keyfi seçimler yapmaz: derleyicinin keyfi bir seçim yapması gereken bir sınıfı tanımlamak bir hatadır.
curiousguy

5

Elmas sorununun bir sorun olduğunu düşünmüyorum, bu safsatayı düşünürdüm, başka bir şey değil.

Benim bakış açıma göre, çoklu kalıtımla ilgili en kötü sorun, RAD - kurbanlar ve geliştirici olduklarını iddia eden ancak gerçekte yarı bilgiyle (en iyi ihtimalle) sıkışmış insanlar.

Kişisel olarak, nihayet Windows Formlarında böyle bir şey yapabilseydim çok mutlu olurdum (bu doğru kod değil, ama size fikri vermeli):

public sealed class CustomerEditView : Form, MVCView<Customer>

Birden fazla mirasa sahip olmamamdaki ana sorun bu. Arayüzlerle benzer bir şey YAPABİLİRSİNİZ, ancak benim "s *** kodu" dediğim şey var, örneğin bir veri bağlamı elde etmek için sınıflarınızın her birine yazmanız gereken bu acı verici tekrarlayan c ***.

Kanımca, modern bir dilde HERHANGİ bir kod tekrarına kesinlikle gerek yok, en ufak bir gerek yok.


Katılıyorum, ancak yalnızca eğilimindeyim: hataları tespit etmek için herhangi bir dilde bir miktar fazlalık olması gerekir. Her neyse, Felix geliştirici ekibine katılmalısınız çünkü bu temel bir hedeftir. Örneğin, tüm bildirimler karşılıklı olarak özyinelemelidir ve ileriye doğru olduğu kadar geriye doğru da görebilirsiniz, böylece ileriye dönük bildirimlere ihtiyaç duymazsınız (kapsam, C goto etiketleri gibi ayarlanır).
Yttrill

Buna tamamen katılıyorum - burada benzer bir sorunla karşılaştım . İnsanlar elmas sorunu hakkında konuşuyorlar, dini olarak alıntı yapıyorlar ama bence bu çok kolay önleniyor. (Hepimizin programlarımızı iostream kitaplığını yazdıkları gibi yazmamıza gerek yoktur.) Örtüşen işlevleri veya işlev adları olmayan iki farklı temel sınıfın işlevselliğine ihtiyaç duyan bir nesneye sahip olduğunuzda mantıksal olarak çoklu kalıtım kullanılmalıdır. Doğru ellerde bu bir araçtır.
jedd.ahyoung

3
@Turing Complete: herhangi bir kod tekrarının olmaması: bu güzel bir fikir ama yanlış ve imkansız. Çok sayıda kullanım modeli var ve ortak olanları kütüphanede soyutlamak istiyoruz, ancak hepsini soyutlamak çılgınlıktır çünkü yapabilsek bile tüm isimleri hatırlayan anlamsal yük çok yüksektir. İstediğin şey güzel bir denge. Unutmayın, şeylere yapı kazandıran şey yinelemedir (örüntü artıklık anlamına gelir).
Yttrill

@ lunchmeat317: Kodun genel olarak 'elmas'ın bir problem oluşturacağı şekilde yazılmaması gerektiği gerçeği, bir dil / çerçeve tasarımcısının konuyu görmezden gelebileceği anlamına gelmez. Bir çerçeve, yukarı ve aşağı yönlü değerlendirme yapmanın nesne kimliğini korumasını sağlıyorsa, bir sınıfın sonraki sürümlerinin, bir kırılma değişikliği olmaksızın değiştirilebilecek türlerin sayısını artırmasına izin vermek istiyor ve çalışma zamanı türü oluşturmaya izin vermek istiyorsa, Yukarıdaki hedefleri karşılarken çoklu sınıf mirasına (arayüz kalıtımının aksine) izin verebileceğini sanmıyorum.
supercat

3

Common Lisp Nesne Sistemi (CLOS), C ++ tarzı problemlerden kaçınırken MI'yı destekleyen bir şeye başka bir örnektir: kalıtıma mantıklı bir varsayılan verilirken, yine de size, örneğin bir süperin davranışını tam olarak nasıl arayacağınıza açıkça karar verme özgürlüğü sağlar. .


Evet, CLOS, modern hesaplamanın başlangıcından bu yana belki de geçmişten bu yana en üstün nesne sistemlerinden biridir :)
rostamn739

2

Çoklu mirasın kendisinde yanlış bir şey yoktur. Sorun, başlangıçtan itibaren çoklu miras akılda tutularak tasarlanmamış bir dile çoklu kalıtım eklemektir.

Eyfel dili, çok verimli ve üretken bir şekilde, kısıtlama olmaksızın çoklu mirası destekliyor, ancak dil o baştan itibaren onu desteklemek için tasarlandı.

Bu özelliğin derleyici geliştiricileri için uygulanması karmaşıktır, ancak bu dezavantajın, iyi bir çoklu kalıtım desteğinin diğer özelliklerin desteğini önleyebilmesi (yani Arayüz veya Genişletme Yöntemine gerek olmaması) ile telafi edilebileceği görülmektedir.

Bence çoklu mirası destekleyip desteklememenin daha çok bir seçim meselesi, bir öncelik meselesi. Daha karmaşık bir özelliğin doğru şekilde uygulanması ve çalıştırılması daha fazla zaman alır ve daha tartışmalı olabilir. C ++ uygulaması, C # ve Java'da çoklu kalıtımın uygulanmamasının nedeni olabilir ...


1
MI için C ++ desteği " çok verimli ve üretken " değil mi?
Meraklıguy

1
Aslında, C ++ 'ın diğer özelliklerine uymaması bakımından biraz bozuk. Devralma, birden fazla kalıtım bir yana, kalıtımla düzgün çalışmıyor (gerçekten kötü kuralları kontrol edin). Elmasları doğru bir şekilde oluşturmak o kadar zor ki, Standartlar komitesi, doğru yapmak yerine basit ve verimli tutmak için istisna hiyerarşisini alt üst etti. Daha eski bir derleyicide bunu test ettiğimde kullanıyordum ve birkaç MI karışımı ve temel istisna uygulamaları bir Megabayt koddan daha pahalıydı ve derlenmesi 10 dakika sürdü .. sadece tanımlar.
Yttrill

1
Elmaslar buna iyi bir örnek. Eyfel'de elmas açıkça çözüldü. Örneğin, Öğrenci ve Öğretmenin hem Kişiden miras aldığını hayal edin. Kişinin bir takvimi vardır, dolayısıyla hem Öğrenci hem de Öğretmen bu takvimi devralır. Hem Öğretmenden hem de Öğrenciden miras alan bir Öğretim Öğrencisi oluşturarak bir elmas oluşturursanız, her iki takvimi de ayrı ayrı tutmak için devralınan takvimden birini yeniden adlandırmaya veya daha çok Kişi gibi davranması için bunları birleştirmeye karar verebilirsiniz. Çoklu miras güzel bir şekilde uygulanabilir, ancak tercihen en baştan dikkatli bir tasarım gerektirir ...
Christian Lemer

1
Eiffel derleyicileri, bu MI modelini verimli bir şekilde uygulamak için küresel bir program analizi yapmak zorundadır. Polimorfik yöntem çağrıları için, burada açıklandığı gibi dağıtıcı thunks veya seyrek matrisler kullanırlar . Bu, C ++ 'ın ayrı derlemesi ve C #' lar ve Java'nın sınıf yükleme özelliği ile iyi karışmaz.
cyco130

2

Java ve .NET gibi çerçevelerin tasarım hedeflerinden biri, derlenmiş kodun önceden derlenmiş bir kitaplığın bir sürümüyle çalışmasını, bu kitaplığın sonraki sürümleriyle eşit derecede iyi çalışmasını sağlamaktır. yeni özellikler ekleyin. C veya C ++ gibi dillerdeki normal paradigma, ihtiyaç duydukları tüm kitaplıkları içeren statik olarak bağlantılı yürütülebilir dosyaları dağıtmak iken, .NET ve Java'daki paradigma, uygulamaları çalışma zamanında "bağlantılı" bileşen koleksiyonları olarak dağıtmaktır. .

.NET'ten önce gelen COM modeli bu genel yaklaşımı kullanmayı denedi, ancak gerçekten mirası yoktu - bunun yerine, her sınıf tanımı hem bir sınıfı hem de tüm ortak üyelerini içeren aynı ada sahip bir arabirimi etkin bir şekilde tanımladı. Örnekler sınıf türündeyken başvurular arabirim türündeydi. Bir sınıfın başka bir sınıftan türetildiğinin bildirilmesi, bir sınıfın diğerinin arabirimini uyguladığını bildirmeye eşdeğerdi ve yeni sınıfın, türetildiği sınıfların tüm genel üyelerini yeniden uygulamasını gerektiriyordu. Y ve Z, X'ten türetilirse ve W, Y ve Z'den türetilirse, Y ve Z'nin X'in üyelerini farklı şekilde uygulamalarının bir önemi yoktur, çünkü Z, uygulamalarını kullanamayacaktır - kendi. W, Y ve / veya Z örneklerini kapsayabilir,

Java ve .NET'teki zorluk, kodun üyeleri devralmasına izin verilmesi ve onlara dolaylı olarak üst üyelere başvuruda bulunmasına izin verilmesidir . Yukarıdaki gibi WZ ile ilgili sınıfların olduğunu varsayalım:

class X { public virtual void Foo() { Console.WriteLine("XFoo"); }
class Y : X {};
class Z : X {};
class W : Y, Z  // Not actually permitted in C#
{
  public static void Test()
  {
    var it = new W();
    it.Foo();
  }
}

Görünüşe W.Test()göre bir W örneği oluşturmak, içinde Footanımlanan sanal yöntemin uygulamasını çağırmalı X. Bununla birlikte, Y ve Z'nin aslında ayrı olarak derlenmiş bir modülde olduğunu ve X ve W derlendiğinde yukarıdaki gibi tanımlanmalarına rağmen, daha sonra değiştirilip yeniden derlendiğini varsayalım:

class Y : X { public override void Foo() { Console.WriteLine("YFoo"); }
class Z : X { public override void Foo() { Console.WriteLine("ZFoo"); }

Şimdi aramanın etkisi ne olmalı W.Test()? Programın dağıtımdan önce statik olarak bağlanması gerekiyorsa, statik bağlantı aşaması, programın Y ve Z değiştirilmeden önce hiçbir belirsizliği olmasa da, Y ve Z'deki değişikliklerin her şeyi belirsiz hale getirdiğini ve bağlayıcının bunu reddedebileceğini fark edebilir. bu tür bir belirsizlik çözülmedikçe veya çözülene kadar programı oluşturun. Öte yandan, hem W hem de Y ve Z'nin yeni sürümlerine sahip olan kişinin, programı çalıştırmak isteyen ve hiçbiri için kaynak kodu olmayan biri olması mümkündür. Ne zaman W.Test()çalıştığı artık net olmayacakW.Test() yapmalı, ancak kullanıcı W'yi Y ve Z'nin yeni sürümüyle çalıştırmayı deneyene kadar, sistemin herhangi bir parçasının bir sorun olduğunu fark etmesi mümkün olmayacaktı (W, Y ve Z'deki değişikliklerden önce bile yasadışı kabul edilmediği sürece) .


2

Elmas Eğer sürece bir sorun değildir do not C ++ sanal miras gibi bir şey kullanın: Her temel sınıf (aslında onlar RAM içinde bu şekilde ortaya koydu) üyesi alanını andıran Normal devralma bazı sözdizimsel şeker veren ve bir daha fazla sanal yöntemi geçersiz kılmak için ekstra yetenek. Bu, derleme sırasında bazı belirsizlikler ortaya çıkarabilir, ancak bu genellikle çözülmesi kolaydır.

Öte yandan, sanal kalıtım ile çok kolay bir şekilde kontrolden çıkar (ve sonra bir karmaşa haline gelir). Örnek olarak bir "kalp" diyagramı düşünün:

  A       A
 / \     / \
B   C   D   E
 \ /     \ /
  F       G
    \   /
      H

C ++ tamamen imkansızdır: yakında kadar Fve Gtek sınıfa birleştirilir, bunların Aler de dönem birleştirilir. Eğer ++ taban sınıfları C matlaşı- düşünün asla Bu demektir ki (bu örnekte sen inşa etmek zorunda Aiçinde Hbilmek zorunda böylece o hiyerarşisinde mevcut yerde). Ancak diğer dillerde işe yarayabilir; örneğin Fve GA'yı açıkça "dahili" olarak ilan edebilir, dolayısıyla sonuçta birleşmeyi yasaklayabilir ve kendilerini etkili bir şekilde sağlamlaştırabilir.

Diğer bir ilginç bir örnektir ( değil C ++ - özel):

  A
 / \
B   B
|   |
C   D
 \ /
  E

Burada yalnızca Bsanal miras kullanır. Yani aynı şeyi paylaşan Eiki Bs içerir A. Bu şekilde, bir elde edebilirsiniz A*işaret ettiği işaretçi E, ancak bir o döküm olamaz B*nesne rağmen pointer ise aslında B böyle dökme belirsiz olarak ve derleyici görür sürece bu belirsizlik (derleme sırasında tespit edilemez tüm program). İşte test kodu:

struct A { virtual ~A() {} /* so that the class is polymorphic */ };
struct B: virtual A {};
struct C: B {};
struct D: B {};
struct E: C, D {};

int main() {
        E data;
        E *e = &data;
        A *a = dynamic_cast<A *>(e); // works, A is unambiguous
//      B *b = dynamic_cast<B *>(e); // doesn't compile
        B *b = dynamic_cast<B *>(a); // NULL: B is ambiguous
        std::cout << "E: " << e << std::endl;
        std::cout << "A: " << a << std::endl;
        std::cout << "B: " << b << std::endl;
// the next casts work
        std::cout << "A::C::B: " << dynamic_cast<B *>(dynamic_cast<C *>(e)) << std::endl;
        std::cout << "A::D::B: " << dynamic_cast<B *>(dynamic_cast<D *>(e)) << std::endl;
        std::cout << "A=>C=>B: " << dynamic_cast<B *>(dynamic_cast<C *>(a)) << std::endl;
        std::cout << "A=>D=>B: " << dynamic_cast<B *>(dynamic_cast<D *>(a)) << std::endl;
        return 0;
}

Dahası, uygulama çok karmaşık olabilir (dile bağlıdır; bencismith'in cevabına bakınız).


MI ile ilgili gerçek sorun budur. Programcılar, bir sınıf içinde farklı çözünürlüklere ihtiyaç duyabilir. Dil çapında bir çözüm, mümkün olanı sınırlar ve programcıları, programın düzgün çalışmasını sağlamak için kludler oluşturmaya zorlar.
shawnhcorey
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.