Java geliştiricileri bilinçli olarak RAII'den vazgeçti mi?


82

Uzun zamandır bir C # programcısı olarak, yakın zamanda Kaynak Edinimi Başlatma'nın (RAII) avantajları hakkında daha fazla şey öğrenmeye başladım . Özellikle, C # deyiminin olduğunu keşfettim:

using (var dbConn = new DbConnection(connStr)) {
    // do stuff with dbConn
}

C ++ eşdeğeri var:

{
    DbConnection dbConn(connStr);
    // do stuff with dbConn
}

yani DbConnectionbir usingblokta olduğu gibi kaynak kullanımını kullanmayı hatırlamak C ++ 'da gereksizdir! Bu C ++ 'nın büyük bir avantajı gibi görünüyor. Eğer türün örneği üyesi olan bir sınıf göz önüne aldığımızda bu daha da ikna edici DbConnectionörneğin,

class Foo {
    DbConnection dbConn;

    // ...
}

C # 'da Foo'nun IDisposableböyle bir uygulamasına ihtiyacım olacak :

class Foo : IDisposable {
    DbConnection dbConn;

    public void Dispose()
    {       
        dbConn.Dispose();
    }
}

Daha da kötüsü, her kullanıcının bir bloğa Fooyerleştirmeyi hatırlaması gerekir , örneğin:Foousing

   using (var foo = new Foo()) {
       // do stuff with "foo"
   }

Şimdi C # 'ya ve Java köklerine baktığımda merak ediyorum ... Java geliştiricileri, yığını yığında bıraktıklarında bıraktıkları şeyleri tam olarak takdir ettiler mi?

(Benzer şekilde, Stroustrup RAII'nin önemini tam olarak takdir etti mi?)


5
Neden C ++ 'a kaynak koymadığınızdan bahsettiğinizden emin değilim. DBConnection nesnesi muhtemelen tüm kaynaklarını imha aracında kapatmayı işler.
maple_shaft

16
@maple_shaft, benim açımdan Bu soruda ele aldığım C ++ 'ın avantajı budur. C # ile kaynakları "kullanma" içine almanız gerekir ... içinde C ++ kullanmazsınız.
JoelFan

12
Anladığım kadarıyla, RAII'nin bir strateji olarak, C ++ derleyicileri aslında Java'dan sonra gelişmiş şablonlama kullanmak için yeterince iyi olduklarında anlaşıldı. Java oluşturulurken kullanılmak üzere aslında sunulmuştu C ++ ile, stil çok ilkel, "sınıfları ile C" oldu belki şanslı olsaydı, temel şablonları.
Sean McMillan

6
“Benim anladığım kadarıyla RAII, bir strateji olarak, C ++ derleyicileri Java'nın hemen ardından olan gelişmiş şablonlamayı kullanmak için yeterince iyi olduklarında yalnızca bir kez anlaşıldı.” - Bu doğru değil. Yapıcılar ve yıkıcılar, şablonların yaygın olarak kullanılmasından çok önce ve Java'dan önce C ++ 'ın temel özellikleridir.
Jim, Teksas

8
@JimInTexas: Bence Sean orada bir yerde temel bir gerçekler tohumuna sahip (Şablonlar olmasa da istisnalar sıkıntı olsa da). İnşaatçılar / Yıkıcılar başından beri oradaydı, ancak öneme sahipti ve RAII kavramı başlangıçta değildi (aradığım kelime). Tüm RAII'nin ne kadar önemli olduğunu fark etmeden önce derleyicilerin iyileşmesi birkaç yıl sürdü.
Martin York

Yanıtlar:


38

Şimdi C # 'ya ve Java köklerine baktığımda merak ediyorum ... Java geliştiricileri, yığını yığında bıraktıklarında bıraktıkları şeyleri tam olarak takdir ettiler mi?

(Benzer şekilde, Stroustrup RAII'nin önemini tam olarak takdir etti mi?)

Gosling’in Java’yı tasarladığı sırada RAII’nin önemini anlamadığından eminim. Görüşmelerinde sık sık jenerik ve operatör aşırı yüklemesinin dışlanma nedenlerinden bahsetti, ancak deterministik yıkıcılardan ve RAII'den asla bahsetmedi.

Yeterince komik, Stroustrup bile deterministik yıkıcıların tasarladıkları zamandaki öneminin farkında değildi. Teklifi bulamıyorum, ancak gerçekten ilgiliyseniz, burada yaptığı röportajlar arasında bulabilirsiniz: http://www.stroustrup.com/interviews.html


11
@maple_shaft: Kısacası, mümkün değil. Deterministik çöp koleksiyonuna sahip olmak için bir yol icat ettiyseniz (genel olarak imkansız görünüyor ve her durumda son on yıldaki tüm GC optimizasyonlarını geçersiz kılıyorsa), yığın destekli nesneleri tanıtmanız gerekir ancak solucanlar: bu nesneler anlamsallık, altyazı (ve dolayısıyla NO polimorfizmi) ile "dilimleme problemi", belki üzerinde önemli kısıtlamalar yapmazsanız veya büyük uyumsuz tip sistem değişiklikleri yapmazsanız işaretçileri sarkarlar. Ve bu sadece kafamın üstünde.

13
@DeadMG: Yani manuel bellek yönetimine geri dönmemizi öneririz. Bu genel olarak programlamaya geçerli bir yaklaşımdır ve elbette deterministik yıkıma izin verir. Ancak bu, hepimiz salak gibi davransak bile, hafıza güvenliği ve iyi tanımlanmış davranış sağlamak isteyen sadece GC ayarıyla ilgili olan bu soruyu cevaplamıyor. Bu, her şey için GC gerektirir ve nesne imhasını manuel olarak başlatmanın bir yolu yoktur (ve mevcut tüm Java kodları en azından eskiye dayanır), bu yüzden ya GC'yi deterministik yaparsınız ya da şansınız kalmaz.

26
@delan. C ++ akıllı işaretçiler manualbellek yönetimi demezdim . Daha deterministik bir ince taneli kontrol edilebilir çöp toplayıcıya benzerler. Doğru kullanılırsa akıllı işaretçiler arıların dizleridir.
Martin York

10
@LokiAstari: Eh, tam GC'den biraz daha az otomatik olduklarını (aslında ne tür bir akıllılık istediğinizi düşünmelisiniz) ve bunları kütüphane olarak uygulamanın ham işaretçiler (ve dolayısıyla manuel bellek yönetimi) gerektirdiğini söyleyebilirim . Ayrıca, kitaplarımdaki çöp toplama için katı bir gereksinim olan döngüsel başvuruları otomatik olarak ele almaktan daha akıllı bir göstergeden haberdar değilim. Akıllı işaretçiler kesinlikle inanılmaz derecede havalı ve kullanışlıdır, ancak yüzleşmek zorundasınızdır, tamamen ve münhasıran bir GC'd dili için bazı garantiler sağlayamazlar (bunları yararlı görüp görmeyeceğinize dair).

11
@delan: Oraya katılmıyorum. Bence deterministik olduklarından GC'den daha otomatik olduklarını düşünüyorum. TAMAM. Verimli olmak için doğru olanı kullandığınızdan emin olmanız gerekir (size bunu vereceğim). std :: weak_ptr, döngüleri mükemmel şekilde ele alır. Döngüler her zaman tükenir, ancak gerçekte bir problem değildir, çünkü temel nesne genellikle istiflenir ve bu gittiğinde istirahati toparlar. Nadir durumlar için bir sorun olabilirdi std :: weak_ptr.
Martin York

60

Evet, C # (ve, eminim, Java) tasarımcıları deterministik sonlandırmaya karşı özellikle karar verdi. Anders Hejlsberg'e 1999-2002 yılları arasında bu defalarca sordum.

İlk olarak, yığın ya da yığın temelli olup olmadığına dayanan bir nesne için farklı anlambilim fikri, her iki dilin birleştirici tasarım hedefine kesinlikle aykırı olduğu ve bu tür programcıları tam anlamıyla çözdüğü yönündedir.

İkincisi, avantajların olduğunu kabul etseniz bile, defter tutmakla ilgili önemli uygulama karmaşıklıkları ve verimsizlikleri vardır. Sen olamaz gerçekten yönetilen bir dilde yığın yığın benzeri nesneler koydu. "Yığın benzeri anlambilim" deme ve önemli bir çalışmaya bağlı kalmanız (değer türleri zaten yeterince zor, karmaşık bir sınıfın örneği olan, referansların tekrar girip tekrar belleğe girmesiyle ilgili bir nesne düşünün).

Bu nedenle, "(neredeyse her şeyin bir nesne olduğu" bir programlama sistemindeki her nesne üzerinde deterministik bir sonlandırma istemezsiniz. Yani do deterministik tamamlanmasını sahiptir birinden normalde izlenen nesneyi ayırmak için programcı kontrollü sözdizimi çeşit tanıtmak zorundayız.

C # 'da, usingC # 1.0 olanın tasarımında oldukça geç gelen bir anahtar kelimeye sahipsiniz . Her IDisposableşey oldukça saçma ve bir kazan-plaka modelinin otomatik olarak uygulanabileceği sınıfları işaretleyen usingC ++ yıkıcı sözdizimi ile çalışmak daha şık olurdu, diye merak ediyor musunuz?~IDisposable


2
Peki ya C ++ / CLI (.NET), yönetilen öbek üzerindeki nesnelerin RIAA sağlayan yığın tabanlı bir "tanıtıcı" da olduğu yerde?
JoelFan

3
C ++ / CLI'nin çok farklı tasarım kararları ve kısıtlamaları var. Bu kararların bazıları, programlayıcılardan hafıza tahsisi ve performans etkileri hakkında daha fazla düşünce isteyebileceğiniz anlamına gelir: “ems için kendilerini asacak kadar ip ver” takası. Ve C ++ / CLI derleyicisinin C # 'dan (özellikle erken nesillerde) çok daha karmaşık olduğunu hayal ediyorum.
Larry OBrien

5
+1 bu şu ana kadarki tek cevap - bu, Java'nın kasıtlı olarak (ilkel olmayan) yığın tabanlı nesnelere sahip olmamasıdır.
BlueRaja - Danny Pflughoeft

8
@Peter Taylor - doğru. Ancak C # 'nın deterministik olmayan yıkıcılarının çok az değere sahip olduğunu düşünüyorum, çünkü herhangi bir kısıtlı kaynağı yönetmek için ona güvenemezsiniz. Yani, benim görüşüme göre, ~sözdizimi sözdizimi için şeker olmak için kullanmak daha iyi olabilirdiIDisposable.Dispose()
Larry OBrien

3
@Larry: Katılıyorum. C ++ / CLI ,~ sözdizimsel şeker olarak kullanır IDisposable.Dispose()ve C # sözdiziminden çok daha uygundur.
dan04, kas

41

Java'nın 1991-1995'te, C ++ 'ın çok farklı bir dil olduğu zaman geliştirildiğini unutmayın. İstisnalar (RAII'yi gerekli kılan ) ve şablonlar (akıllı işaretçilerin uygulanmasını kolaylaştıran) "yeni fanglı" özelliklerdi. Çoğu C ++ programcısı C'den gelmişti ve manuel bellek yönetimi için kullanılıyordu.

Bu yüzden Java'nın geliştiricilerinin kasıtlı olarak RAII'yi bırakmaya karar verdiğinden şüpheliyim. Bununla birlikte, Java'nın değer semantiği yerine referans anlambilimini tercih etmesi kasıtlı bir karardı. Deterministik yıkımı referans anlamsal bir dilde uygulamak zordur.

Öyleyse neden değer anlambilimi yerine referans anlambilimi kullanıyorsunuz?

Çünkü dili çok daha basit hale getiriyor .

  • Arasında bir sözdizimsel ayrım için gerek yoktur Foove Foo*ya arasındaki foo.barve foo->bar.
  • Tüm atamaların bir işaretçiyi kopyalaması gerektiğinde aşırı atama yapılmasına gerek yoktur.
  • Kopyalama kurucularına gerek yoktur. ( Zaman zaman bunun gibi açık bir kopyalama işlevine clone()ihtiyaç duyulabilir. Çoğu nesnenin kopyalanması gerekmez. Örneğin, değiştirilemez.)
  • privateKopya kurucuları ilan etmeye ve operator=bir sınıfı tartışılmaz hale getirmeye gerek yoktur . Bir sınıfın nesnelerinin kopyalanmasını istemiyorsanız, kopyalamak için bir fonksiyon yazmazsınız.
  • swapİşlevlere gerek yoktur . (Bir sıralama rutini yazmıyorsanız.)
  • C ++ 0x stili değer referanslarına gerek yoktur.
  • (N) RVO'ya gerek yoktur.
  • Dilimleme problemi yok.
  • Derleyicinin nesne düzenlerini belirlemesi daha kolaydır, çünkü referanslar sabit bir boyuta sahiptir.

Referans anlambiliğinin ana dezavantajı, her nesne potansiyel olarak kendisine birden fazla referans verdiğinde, ne zaman silineceğini bilmek zorlaşır. Hemen hemen otomatik bellek yönetimine sahip olmalısınız .

Java, deterministik olmayan bir çöp toplayıcıyı kullanmayı seçti.

GC deterministik olamaz mı?

Evet yapabilir. Örneğin, Python'un C uygulaması referans sayımı kullanır. Ve daha sonra yeniden sayımların başarısız olduğu döngüsel çöpleri işlemek için GC izleme eklendi.

Ancak tazminat korkunç verimsizdir. Sayıları güncellemek için çok sayıda CPU döngüsü harcandı. Bu güncellemelerin senkronize edilmesi gereken çok iş parçacıklı bir ortamda (Java'nın tasarlandığı tür gibi) daha da kötüsü. Boş çöp toplayıcısını kullanmak, diğerine geçmek zorunda kalana kadar çok daha iyi .

Java'nın, dosyalar ve soketler gibi elverişsiz kaynaklar pahasına ortak vakayı (belleği) optimize etmeyi seçtiğini söyleyebilirsiniz. Bugün, RAII'nin C ++ 'a benimsenmesi ışığında, yanlış seçim gibi görünebilir. Ancak, Java için hedef kitlenin çoğunun, bu tür şeyleri açıkça kapatmak için kullanılan C (veya "sınıflı C") programcıları olduğunu unutmayın.

Peki ya C ++ / CLI "yığın nesneleri"?

Onlar sadece konum için sözdizimsel şekerDispose ( orijinal bağlantı çok C # gibi) using. Bununla birlikte, genel deterministik imha sorununu çözmez, çünkü bir anonim oluşturabilirsiniz gcnew FileStream("filename.ext")ve C ++ / CLI bunu otomatik olarak elden çıkarmayacak.


3
Ayrıca, güzel bağlantılar (özellikle bu tartışma ile son derece alakalı olan ilki ) .
BlueRaja - Danny Pflughoeft

usingDeyim güzel birçok temizleme ile ilgili sorunları ele fakat diğerleri kalır. Bir dil ve çerçeve için doğru yaklaşımın, başvuruda bulunmayanlardan "sahip" olan depolama yerleri arasında bildirimsel olarak ayrım IDisposableyapmak olacağını; referans verilen bir depolama yerinin üzerine yazmak ya da terk IDisposableetmek, aksi yönde bir direktif olmadığında hedefi atmalıdır.
supercat,

1
"Kopya kuruculara gerek yok" kulağa hoş geliyor, ancak uygulamada fena halde başarısız oluyor. java.util.Date ve Calendar belki de en ünlü örnekleridir. Bundan daha sevimli bir şey yok new Date(oldDate.getTime()).
kevin cline

2
RAw RAII “terk edilmedi”, sadece terk edilemeyecek varolmadı. (başkası) derin bir kopya almayı unutmuş, kaynakların olması gerekmeyen kopyalar arasında paylaşılmasına neden olmuştur.
08’de

20

Java7, C # 'ya benzer bir şey sundu using: “kaynakları denemek” ifadesi

bir tryveya daha fazla kaynak bildiren bir ifade. Bir kaynak , programın bitmesinden sonra kapatılması gereken bir nesnedir. try-With-kaynaklar deyimi her kaynak deyimi sonunda kapalı olmasını sağlar. java.lang.AutoCloseableUygulayan tüm nesneleri içeren , uygulayan herhangi bir nesne, java.io.Closeablekaynak olarak kullanılabilir ...

Bu yüzden, ya bilinçli olarak RAII'yi uygulama yapmamayı seçtiler ya da bu arada fikrini değiştirdiler.


İlginç, ancak bu sadece uygulayan nesnelerle çalışır gibi görünüyor java.lang.AutoCloseable. Muhtemelen önemli bir şey değil ama bunun nasıl kısıtlı hissettirdiğini sevmiyorum. Belki de otomatik olarak yeniden bağlanması gereken başka bir nesneye sahibim, ancak bunu gerçekleştirmek için anlamsal olarak garip AutoCloseable...
Sinir

9
@Patrick: Er, öyle mi? usingRAII ile aynı değildir - bir durumda arayan kişi kaynakları elden çıkarmaktan endişelenir, diğer durumda da arayan elden çıkar.
BlueRaja - Danny Pflughoeft

1
+1 Kaynakları denemeyi bilmiyordum; daha fazla kazan plakası dökülmesinde yararlı olmalıdır.
jprete

3
-1 using/ RAID ile aynı olmayan kaynaklar için deneyin.
Sean McMillan

4
@Sean: Kabul etti. usingve ilk, RAII'nin hiçbir yerinde yok.
DeadMG

18

Java kasıtlı olarak yığın tabanlı nesnelere (aka değer nesneleri) sahip değildir. Bunlar, böyle bir yöntemin sonunda nesnenin otomatik olarak imha edilmesi için gereklidir.

Bu ve Java'nın çöp toplandığı gerçeğinden dolayı, deterministik sonlandırma daha az ya da çok imkansızdır (örneğin. "Yerel" nesnem başka bir yere başvurulmuşsa ne olur? Sonra yöntem bittiğinde, imha edilmesini istemeyiz.) ) .

Bununla birlikte, bu çoğumuz için iyidir, çünkü yerel (C ++) kaynaklarla etkileşimde bulunmak dışında, neredeyse hiçbir zaman deterministik sonlandırmaya ihtiyaç duyulmaz !


Java neden yığın tabanlı nesnelere sahip değil?

(İlkellerden başka ..)

Çünkü yığın tabanlı nesneler yığın tabanlı referanslardan farklı semantiklere sahiptir. Aşağıdaki kodu C ++ 'da hayal edin; bu ne işe yarıyor?

return myObject;
  • Eğer myObjectyerel bir yığın tabanlı nesnedir (sonuç şeye atanmışsa), kopyalamaya karşı yapıcı denir.
  • Eğer myObjectbir yerel yığın tabanlı nesne ve biz bir başvuru dönen, sonuç tanımsızdır.
  • Eğer myObjectbir üye / küresel nesnedir (sonuç şeye atanmışsa), kopyalamaya karşı yapıcı denir.
  • Eğer myObjectbir üye / küresel nesne ve biz bir başvuru dönen referans döndürülür.
  • Eğer myObjectyerel yığın tabanlı bir nesne için bir gösterici, tanımlanmamış.
  • Eğer myObjectbir üye / küresel nesneye bir işaretçi, yani gösterici döner.
  • Eğer myObjectbir bellek tabanlı bir nesne için bir gösterici, bu işaretçi döndürülür.

Şimdi aynı kod Java’da ne yapıyor?

return myObject;
  • Referans myObjectgeri döndü. Değişkenin yerel, üye veya global olması önemli değil; ve endişelenecek yığın tabanlı nesneler veya işaretçi vakaları yoktur.

Yukarıda, yığın tabanlı nesnelerin neden C ++ 'da programlama hatalarının çok yaygın bir nedeni olduğu gösterilmektedir. Bu nedenle, Java tasarımcıları onları çıkardı; ve onlarsız, Java’da RAII kullanmanın bir anlamı yoktur.


6
"RAII'de bir nokta yok" derken neyi kastettiğinizi bilmiyorum ... "Sanırım" Java'da RAII sağlama yeteneği yok "demek istediniz ... RAII herhangi bir dilden bağımsızdır ... "anlamsız" olun, çünkü 1 belirli dil bunu sağlamaz
JoelFan

4
Bu geçerli bir sebep değil. Yığın tabanlı RAII kullanmak için bir nesnenin yığında yaşaması gerekmez. "Eşsiz referans" diye bir şey varsa, yıkıcı kapsam dışına çıktığında ateşlenebilir. Örneğin, D programlama diliyle nasıl çalıştığını görün: d-programming-language.org/exception-safe.html
Nemanja Trifunovic

3
@Nemanja: Bir nesne değil sahip yığın tabanlı anlambilim olması yığın yaşamak ve bunu yaptım demedim. Ancak sorun bu değil; Bahsettiğim gibi, sorun yığın temelli anlambilimdir.
BlueRaja - Danny Pflughoeft

4
@Aaronaught: Şeytan "neredeyse her zaman" ve "çoğu zaman" içindedir. Eğer db bağlantınızı kapatmazsanız ve sonlandırıcıyı tetiklemek için onu GC'ye bırakırsanız, ünite testleriniz için gayet iyi çalışır ve üretimde konuşlandırıldığında ciddi şekilde kopar. Deterministik temizlik dilden bağımsız olarak önemlidir.
Nemanja Trifunovic

8
@NemanjaTrifunovic: Neden canlı bir veritabanı bağlantısı üzerinde test yapıyorsunuz? Bu gerçekten bir birim testi değil. Hayır, üzgünüm, satın almıyorum. Eğer kurucular veya özellikler sayesinde bunları geçen gerektiğini zaten her yerde DB bağlantıları oluşturmak olmamalı, ve sizin gelir yok yığın benzeri Otomatik imha anlambilim istiyorum. Veri tabanı bağlantısına bağlı çok az sayıda nesne aslında kendisine ait olmalıdır. Deterministik olmayan temizlik sizi sık sık ısırıyorsa, o kadar zor, o zaman kötü bir uygulama tasarımından dolayı, kötü dil tasarımından değil.
Aaron

17

Deliklerini açıklamanız usingeksik. Aşağıdaki sorunu göz önünde bulundurun:

interface Bar {
    ...
}
class Foo : Bar, IDisposable {
    ...
}

Bar b = new Foo();

// Where's the Dispose?

Bence hem RAII hem de GC'ye sahip olmamak kötü bir fikirdi. Java dosyaları kapanış gelince, bu kadar malloc()ve free()orada.


2
RAII'nin arıların dizleri olduğuna katılıyorum. Ancak usingfıkra, C # üstü Java için atılmış büyük bir adım. Deterministik yıkıma ve dolayısıyla kaynak yönetimine izin verir (bunu yapmak için hatırlamanız gerektiği kadar RAII kadar iyi değil, ama kesinlikle iyi bir fikir).
Martin York

8
“Java ile dosyaları kapatmaya gelince, orası malloc () ve ücretsiz ().” - Kesinlikle.
Konrad Rudolph

9
@KonradRudolph: Malloctan ve serbestten daha kötü. En azından C de istisnaların yok.
Nemanja Trifunoviç

1
@Nemanja: Dürüst olalım, free()içinde yapabilirsiniz finally.
DeadMG

4
@Loki: Temel sınıf problemi problem olarak çok daha önemlidir. Örneğin, orijinal IEnumerablemiras almadı IDisposableve sonuç olarak asla uygulanamayan bir sürü özel yineleyici vardı.
DeadMG

14

Ben çok yaşlıyım. Orada bulundum ve gördüm ve kafamı çok sık çarptım.

IBM erkeklerinin bize bu yeni Java dilinin ne kadar harika olduğunu anlattıkları Hursley Park'taki bir konferanstaydım, sadece birisi sordu ... neden bu nesneler için bir yıkıcı yok. C ++ 'da yıkıcı olarak bildiğimiz bir şeyi kastetmedi, ancak sonlandırıcı da yoktu (ya da sonlandırıcıları vardı ama temelde işe yaramadı). Bu çok geriye döndü ve Java'nın bu noktada biraz oyuncak dil olduğuna karar verdik.

Şimdi de finalizatörleri dilin teknik özelliklerine eklediler ve Java bazı uyarlamalar gördü.

Tabii ki, daha sonra herkese finalistleri nesnelerine koymadıkları söylendi çünkü GC'yi inanılmaz derecede yavaşlattı. (yalnızca yığını kilitlemekle kalmayıp, sonlandırılacak nesneleri geçici bir alana taşımak zorunda kaldığından, bu yöntemler GC'nin uygulamayı çalıştırmasını duraklattığı için çağrılamaz. Bunun yerine hemen sonraki çağrılırlar. GC döngüsü) (ve daha da kötüsü, uygulama kapatıldığında bazen sonlandırıcı hiçbir zaman hiç aranmaz. Dosya tanıtıcınızın hiç kapalı olmadığını hayal edin)

Sonra C # 'ye sahip olduk ve MSDN'deki tartışma forumunu hatırlıyorum. Birisi neden deterministik bir sonlandırma olmadığını sordu ve MS çocukları bize bu tür şeylere nasıl ihtiyaç duymadığımızı söylediler, sonra bize uygulamalar tasarlama biçimimizi değiştirmemiz gerektiğini söylediler, sonra bize GC'nin ne kadar şaşırtıcı olduğunu ve tüm eski uygulamalarımızın nasıl olduklarını anlattılar Çöp ve tüm dairesel referanslar nedeniyle hiç çalışmadı. Sonra baskıya girdiler ve bu İddialı paterni kullanabileceğimiz şartnameye eklediklerini söylediler. Bu noktada C # uygulamalarında bizim için manuel bellek yönetimine döndüğümüzü sanıyordum.

Tabii ki, MS'li çocuklar daha sonra bize anlattıklarının hepsinin ... iyi olduğunu keşfettiler, IDispose'ı standart bir arayüzden biraz daha fazla yaptılar ve daha sonra use deyimini eklediler. W00t! Deterministik sonlandırmanın sonuçta dilden eksik bir şey olduğunu fark ettiler. Tabii ki, yine de her yere koymayı hatırlamalısın, bu yüzden hala biraz el kitabı, ama daha iyi.

Öyleyse neden baştan beri her bir kapsam bloğuna otomatik olarak stil anlambilgisi yerleştirmiş olurlarsa bunu yaptılar? Muhtemelen verimlilik, ama sadece farkına varmadıklarını düşünmekten hoşlanıyorum. Sonunda olduğu gibi hala NET'te akıllı işaretçilere ihtiyacınız olduğunu anladılar (google SafeHandle) GC'nin gerçekten tüm sorunları çözeceğini düşünüyorlardı. Bir nesnenin sadece bellekten daha fazlası olduğunu ve GC'nin öncelikle bellek yönetimini idare etmek için tasarlandığını unutmuşlardır. GC'nin bunu halledeceği fikrine kapıldılar ve oraya başka şeyler koyduğunuzu unuttular, bir nesne sadece bir süre silmediğiniz önemli olmayan bir bellek bloğu değildir.

Ancak, orijinal Java’daki bir sonlandırma yönteminin olmamasının da bundan biraz daha fazlası olduğunu düşünüyorum - yarattığınız nesnelerin tamamen bellekle ilgisi olduğunu ve başka bir şey silmek istiyorsanız (bir DB tutacağı veya bir soket veya başka bir şey gibi) ) o zaman manuel olarak yapmanız bekleniyordu .

Java'nın, insanların pek çok manuel tahsisatlı C kodu yazmaya alışkın olduğu gömülü ortamlar için tasarlandığını, bu nedenle otomatik olarak özgür olmamanın çok fazla bir sorun olmadığını - daha önce hiç yapmadıklarını, neden Java’ya ihtiyacınız olduğunu hatırlayın. Sorun, iş parçacığı ya da yığın / öbek ile ilgili bir şey değildi, muhtemelen bellek ayırmayı (ve dolayısıyla ayırmayı) biraz daha kolaylaştırmak için oradaydı. Sonuç olarak, try / finally deyimi, bellek dışı kaynakları işlemek için muhtemelen daha iyi bir yerdir.

Yani IMHO, .NET'in basitçe Java'nın en büyük kusurunu kopyalaması en büyük zayıflığı. .NET daha iyi bir C ++ olmalıydı, daha iyi bir Java.


IMHO, 'blok kullanmak' gibi şeyler deterministik temizlik için doğru yaklaşımdır, ancak birkaç şey daha gerekli: (1) yıkıcıları istisna atarsa ​​nesnelerin atılmasını sağlama aracı; (2) bir direktifle Disposeişaretlenmiş tüm alanları çağırmak için otomatik olarak bir rutin bir yöntem üretme usingve IDisposable.Disposebunu otomatik olarak aramanın gerekip gerekmediğini belirten bir araç ; (3) bir istisna olması durumunda using, ancak buna benzer olan bir yönerge Dispose; (4) bir varyasyonu, IDisposablebir Exceptionparametresini alacaktır ve ...
supercat,

... usingeğer uygunsa, otomatik olarak kullanılacaktır ; Parametre null, usingblok normal şekilde çıkmışsa ya da istisna yoluyla çıkmışsa hangi istisnanın beklediğini gösterir. Böyle şeyler varsa, kaynakları etkin bir şekilde yönetmek ve sızıntıları önlemek çok daha kolay olacaktır.
supercat,

11

"Java'da Düşünme" ve "C ++ 'da Düşünme" ve C ++ Standartlar Komitesinin bir üyesi olan Bruce Eckel, birçok alanda (sadece RAII değil) Gosling ve Java ekibinin yapmadığını düşünüyor. ev ödevi.

... Dilin hem hoş olmayan hem de karmaşık olabileceğini ve aynı zamanda iyi tasarlandığını anlamak için, C ++ 'da her şeyin askıda kaldığı ana tasarım kararını aklınızda tutmalısınız: C. Stroustrup karar verdi - ve doğru bir şekilde Görünüşe göre C programcı kitlelerinin nesnelere hareket etmesini sağlamanın yolu, hareketi saydamlaştırmaktı: C kodlarını C ++ 'da değiştirmeden derlemelerini sağlamak. Bu muazzam bir kısıtlamadı ve her zaman C ++ 'ın en büyük gücü ... ve dezavantajı olmuştur. C ++ 'ı olduğu kadar başarılı ve olduğu kadar karmaşık yapan şey bu.

Ayrıca C ++ 'ı yeterince iyi anlamayan Java tasarımcılarını kandırdı. Örneğin, operatörlerin aşırı yüklenmesinin programcıların doğru kullanması için çok zor olduğunu düşünüyorlardı. Temel olarak C ++ için doğrudur, çünkü C ++ hem yığın ayırma hem de yığın ayırma işlemine sahiptir ve tüm durumları ele almak ve bellek sızıntısına neden olmamak için operatörleri aşırı yüklemelisiniz. Gerçekten zor. Bununla birlikte, Java, C # 'da gösterildiği gibi (ancak Java'dan önce Python'da gösterilmişti) operatörün aşırı derecede aşırı yüklenmesine neden olan tek bir depolama ayırma mekanizmasına ve bir çöp toplayıcısına sahiptir. Ancak uzun yıllar boyunca, Java ekibinden gelen kısmen “Operatör aşırı yüklemesi çok karmaşık” tır. Bu ve birçoğunun açıkça yapmadığı birçok karar

Başka pek çok örnek var. İlkel "verimlilik için dahil edilmek zorundaydı." Doğru cevap, "her şey bir nesnedir" dürüst olmak ve verimlilik gerektiğinde daha düşük seviyeli faaliyetler yapmak için bir tuzak kapısı sağlamaktır (bu, sıcak nokta teknolojilerinin işleri daha şeffaf hale getirmelerine olanak tanıyacaktı. Sahip olmak). Ve kayan nokta işlemciyi aşkın işlevleri hesaplamak için doğrudan kullanamamanız gerçeği (bunun yerine yazılımda yapılır). Elimden geldiğince bu tür konular hakkında yazdım ve duyduğum cevap her zaman "Java yolu bu" etkisine ilişkin bazı totolojik cevaplar oldu.

Jeneriklerin ne kadar kötü bir şekilde tasarlandıklarını yazdığımda, aynı cevabı aldım, "Java'da verilen önceki (kötü) kararlarla geriye dönük olarak uyumlu olmalıyız". Son zamanlarda gittikçe daha fazla sayıda insan Generics ile kullanımlarının gerçekten çok zor olduğunu görmek için yeterli deneyime sahip oldu - gerçekten, C ++ şablonları çok daha güçlü ve tutarlı (ve derleyici hata mesajlarının tolere edilebildiği için artık kullanımı çok daha kolay). İnsanlar yeniden birleşmeyi bile ciddiye alıyorlardı - faydalı olacak ancak kendisinin empoze ettiği kısıtlamalar tarafından saklanan bir tasarımda bu kadar fazla diş açmayacak bir şey.

Liste çok sıkıcı olduğu noktaya gidiyor ...


5
Bu, RAII'ye odaklanmak yerine, Java'ya karşı C ++ cevabına benziyor. C ++ ve Java'nın her biri güçlü ve zayıf yönleri olan farklı diller olduğunu düşünüyorum. Ayrıca C ++ tasarımcıları ödevlerini pek çok alanda yapmadılar (KISS prensibi uygulanmadı, eksik sınıflar için basit ithalat mekanizması). Ancak sorunun odak noktası RAII idi: Java'da bu eksik ve bunu manuel olarak programlamanız gerekiyor.
Giorgio

4
@Giorgio: Makalenin amacı, Java'nın bazıları RAII ile ilgili olan birkaç konuda tekneyi kaçırdığı görülüyor. Eckels, C ++ ve Java üzerindeki etkisine ilişkin olarak şunları söylüyor: "C ++ 'da her şeyin askıda kaldığı ana tasarım kararını aklınızda tutmalısınız: C ile uyumluluk Ayrıca, C ++ 'ı yeterince iyi anlamayan Java tasarımcılarını da kandırdı. " C ++ tasarımı Java'yı doğrudan etkilerken, C # her ikisinden de öğrenme fırsatı buldu. (Bunu
yapıp yapmadığı

2
@Giorgio Mevcut dilleri belirli bir paradigma ve dil ailesinde çalışmak, yeni dil gelişimi için gereken ödevin bir parçası. Bu, basitçe Java ile kokladıkları bir örnek. Onlar bakmak için C ++ ve Smalltalk vardı. C ++ geliştirildiği zamana bakmak için Java kullanmıyordu.
Jeremy

1
@Gnawme: "Java, bazıları doğrudan RAII ile ilgili olan birkaç konuda tekneyi kaçırmış görünüyor": bu sorunlardan bahseder misiniz? Gönderdiğiniz makale RAII'den bahsetmiyor.
Giorgio

2
@Giorgio Elbette, C ++ 'ın geliştirilmesinden bu yana eksik bulduğunuz özelliklerin birçoğunu hesaba katan yenilikler var. C ++ 'nın geliştirilmesinden önce oluşturulan dillere bakarak bulması gereken bu özelliklerden herhangi biri var mı? Java ile konuştuğumuz türden bir ev ödevi - Java'nın gelişimindeki her C ++ özelliğini göz önünde bulundurmamaları için hiçbir neden yok. Bazıları kasten dışladıkları çoklu kalıtım gibi - diğerleri RAII gibi görmezden gelmiş görünüyorlar.
Jeremy

10

En iyi neden, buradaki cevapların çoğundan daha basittir.

Yığın ayrılmış nesneleri diğer iş parçacıklarına iletemezsiniz.

Dur ve bunu düşün. Düşünmeye devam et .... Şimdi C ++ 'da herkes RAII' de bu kadar keskinleştiği zaman konu yoktu. Erlang bile (diş başına ayrı yığınlar), etrafta çok fazla nesne geçtiğinde cimri olur. C ++ yalnızca C ++ 2011'de bir bellek modeline sahipti; Şimdi derleyicinizin "belgelerine" başvurmak zorunda kalmadan C ++ 'daki eşzamanlılık hakkında neredeyse neden olabilirsiniz.

Java (neredeyse) günden itibaren birden fazla konu için tasarlanmıştır.

Stroustrup'un bana konuya ihtiyacım olmayacağına dair garanti verdiği eski "C ++ Programlama Dili" nin eski kopyasını aldım.

İkinci acı sebep dilimlemekten kaçınmak.


1
Birden fazla iş parçacığı için tasarlanan Java, GC'nin neden referans saymaya dayanmadığını da açıklar.
dan04

4
@NemanjaTrifunovic: C ++ / CLI'yi Java veya C # ile karşılaştıramazsınız, neredeyse yönetilmeyen C / C ++ kodu ile birlikte çalışmayı ifade etmek amacıyla tasarlandı; .NET çerçevesine tam tersi şekilde erişme izni veren, yönetilmeyen bir dil gibi.
Aaron,

3
@NemanjaTrifunovic: Evet, C ++ / CLI, normal uygulamalar için tamamen uygunsuz bir şekilde nasıl yapılabileceğine bir örnektir . Bu var sadece C / C ++ birlikte çalışma için faydalıdır. Sadece normal bir geliştiriciler gerekir değil tamamen alakasız "yığın veya yığın" kararı ile palan gerekir, ama hiç refactor çalışırsanız o zaman yanlışlıkla boş gösterici / başvuru hata ve / veya bir bellek sızıntısı oluşturmak trivially kolaydır. Üzgünüm, ama Java veya C # ile programlanmış olup olmadığını merak etmem gerekiyor, çünkü gerçekten kimsenin C ++ / CLI'da kullanılan anlambilimi isteyeceğini sanmıyorum .
Aaron,

2
@Aaronaught: Hem Java (biraz) hem de C # (çok) ile programladım ve şu anki projem neredeyse hepsi C #. İnan bana, neden bahsettiğimi biliyorum ve "yığın vs. öbek" ile ilgisi yok - ihtiyaç duymadığınız anda tüm kaynaklarınızın serbest bırakıldığından emin olmak için yapması gereken her şey var. Otomatik olarak. Eğer böyle değilse - Sen olacak derde girer.
Nemanja Trifunovic

4
@NemanjaTrifunovic: Bu harika, gerçekten harika, ama hem C # hem de C ++ / CLI, bunun olmasını istediğinizde açıkça belirtmenizi gerektiriyor, sadece farklı bir sözdizimi kullanıyorlar. Şu an için uğraştığınız asıl konuya kimse itiraz etmiyor (“kaynaklar ihtiyaç duymadığı anda kaynakların serbest kalması”) ancak yönetilen tüm dillerin sadece otomatik ama sadece sahip olmaları için devasa bir mantıksal adım atıyorsunuz. çağrı yığını tabanlı belirleyici elden çıkarma işlemi ". Sadece su tutmuyor.
Aaron,

5

C ++ 'ta, daha yüksek seviyeli bir tane (RAII) uygulamak için daha genel amaçlı, daha düşük seviyeli dil özellikleri (yığın tabanlı nesnelerde otomatik olarak çağrılan yıkıcılar) kullanırsınız ve bu yaklaşım C # / Java milletinin görünmediği bir şeydir. çok düşkünüm. Özel ihtiyaçlar için özel üst düzey araçlar tasarlamayı ve dilleri hazırlamış programcılara sunmayı tercih ediyorlardı. Bu tür özel araçlarla ilgili sorun, kişiselleştirilmelerinin çoğu zaman imkansız olmalarıdır (kısmen öğrenmelerini kolaylaştıran şey budur). Küçük bloklardan inşa ederken, zaman içinde daha iyi bir çözüm ortaya çıkabilir, ancak yalnızca yüksek seviyeli yerleşik yapılara sahipseniz , bu daha az olasıdır.

Yani evet, sanırım (aslında orada değildim ...) dillerin daha kolay anlaşılmasını sağlamak amacıyla bilinçli bir karardı, ama bence kötü bir karardı. Sonra tekrar, genellikle C ++ 'ın programcılarına kendi felsefelerini alma şansını vermelerini tercih ediyorum, bu yüzden biraz önyargılıyım.


7
“Programcılara-bir-şansını kendilerine götürme şansı”, her biri kendi dize sınıflarını ve akıllı işaretçilerine sahip programcılar tarafından yazılan kütüphaneleri birleştirmeniz için UNTIL'e iyi bir şekilde çalışıyor.
dan04

@ dan04 böylece size önceden tanımlanmış string sınıfları veren yönetilen diller, daha sonra farklı bir kendinden geçmiş string ile baş edemeyen biri iseniz felaket için bir reçete olan maymunları yamalamanıza izin verir. sınıf.
gbjbaanb

-1

Zaten buna benzer kaba bir eşdeğerini C # Disposeyöntemiyle çağırdınız . Java da vardır finalize. NOT: Java'nın sonlandırmasının deterministik ve farklı olduğunu fark Disposeettim, sadece ikisinin de GC ile birlikte bir temizlik yöntemine sahip olduğuna işaret ediyorum.

Eğer bir şey C ++ daha fazla acı çekerse, bir nesnenin fiziksel olarak imha edilmesi gerekir. C # ve Java gibi daha yüksek seviyelerde, artık referans olmadığında temizlemek için bir çöp toplayıcısına güveniyoruz. C ++ 'daki DBConnection nesnesinin hileli referansları ya da işaretçileri olmadığının garantisi yoktur.

Evet, C ++ kodunu okumak daha sezgisel olabilir, ancak hata ayıklamak için bir kabus olabilir çünkü Java gibi dillerin yerine getirdiği sınırlar ve sınırlamalar, diğer geliştiricilerin ortak çaylak hatalarından korunmasının yanı sıra daha ağırlaştırıcı ve zor hatalardan bazılarını da ortadan kaldırır.

Belki bazıları C + 'nın düşük seviyeli gücü, kontrolü ve saflığı gibi tercihlerimden kaynaklanmaktadır, ki burada benim gibi diğerleri daha açık olan daha sandboxed bir dili tercih etmektedir.


12
Her şeyden önce Java'nın "sonuçlandırmak" nin o ... deterministik olmayan olduğu değil eşdeğer C # 'ın 'elden' ya da C ++ ait' Eğer .NET kullanıyorsanız ayrıca, C ++ da bir çöp toplayıcısı var ... ler yıkıcılar
JoelFan

2
@DeadMG: Sorun şu ki, bir aptal olmayabilirsin, ancak şirketi yeni terk eden (ve şu anda sakladığın kodu yazan) başka biri olabilirdi.
Kevin

7
O adam ne yaparsan yap boktan kod yazacak. Kötü bir programcı alamaz ve ona iyi kod yazmasını sağlayamazsınız. Sarkan işaretçiler, aptallarla uğraşırken endişelerimin en küçüğüdür. İyi kodlama standartları, izlenmesi gereken bellek için akıllı işaretçiler kullanır, bu nedenle akıllı yönetim belleğin nasıl güvenle ayrılacağını ve erişileceğini açıkça belirtmelidir.
DeadMG

3
DeadMG ne dedi? C ++ ile ilgili birçok kötü şey var. Ancak RAII uzun zamandır bunlardan biri değil. Aslında, Java ve .NET'in kaynak yönetimini düzgün bir şekilde hesaba katamaması (tek bellek bellektir, çünkü?) En büyük sorunlarından biridir.
Konrad Rudolph

8
Bence sonlandırıcı, akıllıca bir felaket tasarımdır. Bir nesnenin tasarımcıdan nesnenin kullanıcısına doğru kullanımını zorladığınız için (bellek yönetimi değil kaynak yönetimi). C ++ 'da kaynak yönetimini doğru yapmak (yalnızca bir kez yapılır) sınıf tasarımcısının sorumluluğundadır. Java'da kaynak yönetimini doğru yapmak sınıf kullanıcısının sorumluluğundadır ve bu nedenle kullandığımız her sınıfta yapılması gerekir. stackoverflow.com/questions/161177/…
Martin York
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.