Sebagai seorang programmer C # lama, saya baru-baru ini datang untuk belajar lebih banyak tentang keuntungan dari Resource Acquisition Is Inisialisasi (RAII). Secara khusus, saya telah menemukan bahwa idiom C #:
using (var dbConn = new DbConnection(connStr)) {
// do stuff with dbConn
}
memiliki setara C ++:
{
DbConnection dbConn(connStr);
// do stuff with dbConn
}
artinya, mengingat untuk menyertakan penggunaan sumber daya seperti DbConnection
dalam sebuah using
blok tidak perlu dalam C ++! Ini tampaknya merupakan keunggulan utama C ++. Ini bahkan lebih meyakinkan ketika Anda mempertimbangkan kelas yang memiliki anggota tipe instance, DbConnection
misalnya
class Foo {
DbConnection dbConn;
// ...
}
Dalam C # saya perlu menerapkan Foo IDisposable
seperti:
class Foo : IDisposable {
DbConnection dbConn;
public void Dispose()
{
dbConn.Dispose();
}
}
dan yang lebih buruk, setiap pengguna Foo
harus ingat untuk melampirkan Foo
di using
blok, seperti:
using (var foo = new Foo()) {
// do stuff with "foo"
}
Sekarang melihat C # dan akar Java-nya, saya bertanya-tanya ... apakah pengembang Jawa sepenuhnya menghargai apa yang mereka berikan ketika mereka meninggalkan tumpukan demi tumpukan, sehingga meninggalkan RAII?
(Demikian pula, apakah Stroustrup sepenuhnya menghargai pentingnya RAII?)
sumber
Jawaban:
Saya cukup yakin Gosling tidak mendapatkan arti penting RAII pada saat ia mendesain Java. Dalam wawancaranya, dia sering berbicara tentang alasan untuk tidak menggunakan obat generik dan overloading operator, tetapi tidak pernah menyebutkan destruktor deterministik dan RAII.
Cukup lucu, bahkan Stroustrup tidak menyadari pentingnya destruktor deterministik pada saat ia merancang mereka. Saya tidak dapat menemukan kutipan, tetapi jika Anda benar-benar tertarik, Anda dapat menemukannya di antara wawancaranya di sini: http://www.stroustrup.com/interviews.html
sumber
manual
manajemen memori pointer pintar C ++ . Mereka lebih seperti pengumpul sampah terkendali butir halus terkendali. Jika digunakan dengan benar, smart pointer adalah lutut lebah.Ya, para perancang C # (dan, saya yakin, Java) secara khusus memutuskan menentang finalisasi deterministik. Saya bertanya kepada Anders Hejlsberg tentang hal ini beberapa kali sekitar tahun 1999-2002.
Pertama, gagasan semantik yang berbeda untuk suatu objek berdasarkan apakah berbasis stack atau heap tentu saja bertentangan dengan tujuan desain pemersatu dari kedua bahasa, yang untuk meringankan programmer dari masalah yang persis seperti itu.
Kedua, bahkan jika Anda mengakui bahwa ada keuntungan, ada kompleksitas implementasi yang signifikan dan inefisiensi yang terlibat dalam pembukuan. Anda tidak dapat benar - benar meletakkan objek seperti tumpukan di tumpukan dalam bahasa yang dikelola. Anda dibiarkan mengatakan "semantik mirip tumpukan," dan berkomitmen untuk pekerjaan yang signifikan (tipe nilai sudah cukup keras, pikirkan tentang objek yang merupakan turunan dari kelas kompleks, dengan referensi yang masuk dan kembali ke memori yang dikelola).
Karena itu, Anda tidak ingin finalisasi deterministik pada setiap objek dalam sistem pemrograman di mana "(hampir) semuanya adalah objek." Jadi Anda jangan harus memperkenalkan beberapa jenis sintaks programmer yang dikendalikan untuk memisahkan objek biasanya-dilacak dari satu yang memiliki finalisasi deterministik.
Di C #, Anda memiliki
using
kata kunci, yang masuk agak terlambat dalam desain apa yang menjadi C # 1.0. SeluruhIDisposable
hal yang cukup celaka, dan satu keajaiban apakah itu akan lebih elegan untuk memilikiusing
pekerjaan dengan sintaks destructor C ++~
menandai kelas-kelas dimana boiler-piringIDisposable
pola bisa diterapkan secara otomatis?sumber
~
sintaksis untuk menjadi gula sintaksis untukIDisposable.Dispose()
~
sebagai gula sintaksisIDisposable.Dispose()
, dan jauh lebih nyaman daripada sintaksis C #.Perlu diingat bahwa Java dikembangkan pada 1991-1995 ketika C ++ adalah bahasa yang jauh berbeda. Pengecualian (yang membuat RAII penting ) dan templat (yang membuatnya lebih mudah untuk mengimplementasikan smart pointer) adalah fitur "baru-ketinggalan jaman". Kebanyakan programmer C ++ berasal dari C dan digunakan untuk melakukan manajemen memori manual.
Jadi saya ragu bahwa pengembang Java sengaja memutuskan untuk meninggalkan RAII. Namun, itu adalah keputusan yang disengaja untuk Jawa untuk lebih memilih semantik referensi daripada semantik nilai. Kerusakan deterministik sulit diimplementasikan dalam bahasa referensi-semantik.
Jadi mengapa menggunakan semantik referensi alih-alih nilai semantik?
Karena itu membuat bahasanya jauh lebih sederhana.
Foo
danFoo*
atau antarafoo.bar
danfoo->bar
.clone()
. Banyak objek tidak perlu disalin. Misalnya, tidak bisa diubah.)private
copy constructor danoperator=
membuat kelas tidak dapat di-copy. Jika Anda tidak ingin objek kelas disalin, Anda tidak perlu menulis fungsi untuk menyalinnya.swap
fungsi. (Kecuali jika Anda menulis semacam rutinitas.)Kelemahan utama dari referensi semantik adalah bahwa ketika setiap objek berpotensi memiliki banyak referensi, menjadi sulit untuk mengetahui kapan harus menghapusnya. Anda harus memiliki manajemen memori otomatis.
Java memilih untuk menggunakan pengumpul sampah yang tidak menentukan.
Tidak bisakah GC menjadi deterministik?
Ya bisa. Misalnya, implementasi C dari Python menggunakan penghitungan referensi. Dan kemudian menambahkan tracing GC untuk menangani sampah siklik di mana penghitungan ulang gagal.
Tetapi penghitungan ulang sangat tidak efisien. Banyak siklus CPU dihabiskan untuk memperbarui hitungan. Lebih buruk lagi di lingkungan multi-threaded (seperti jenis Java yang dirancang untuk) di mana pembaruan tersebut perlu disinkronkan. Jauh lebih baik untuk menggunakan pengumpul sampah nol hingga Anda perlu beralih ke yang lain.
Anda dapat mengatakan bahwa Java memilih untuk mengoptimalkan case umum (memori) dengan mengorbankan sumber daya yang tidak dapat dipertukarkan seperti file dan soket. Hari ini, mengingat adopsi RAII di C ++, ini mungkin pilihan yang salah. Tetapi ingat bahwa sebagian besar target audiens untuk Java adalah programmer C (atau "C dengan kelas") yang terbiasa menutup hal-hal ini secara eksplisit.
Tapi bagaimana dengan C ++ / CLI "stack objek"?
Itu hanya gula sintaksis untuk
Dispose
( tautan asli ), seperti halnya C #using
. Namun, itu tidak menyelesaikan masalah umum dari kerusakan deterministik, karena Anda dapat membuat anonimgcnew FileStream("filename.ext")
dan C ++ / CLI tidak akan secara otomatis membuangnya.sumber
using
pernyataan menangani banyak masalah yang berhubungan dengan pembersihan baik, tetapi banyak orang lain tetap. Saya akan menyarankan bahwa pendekatan yang tepat untuk bahasa dan kerangka kerja adalah untuk secara deklaratif membedakan antara lokasi penyimpanan yang "memiliki" referensiIDisposable
dari yang tidak; menimpa atau meninggalkan lokasi penyimpanan yang memiliki referensiIDisposable
harus membuang target dengan tidak adanya arahan yang bertentangan.new Date(oldDate.getTime())
.Java7 memperkenalkan sesuatu yang mirip dengan C #
using
: Pernyataan coba-dengan-sumber dayaJadi saya kira mereka tidak secara sadar memilih untuk tidak mengimplementasikan RAII atau mereka berubah pikiran sementara itu.
sumber
java.lang.AutoCloseable
. Mungkin bukan masalah besar tapi saya tidak suka bagaimana ini terasa agak terkendala. Mungkin saya memiliki beberapa objek lain yang harus dilepaskan secara otomatis, tetapi sangat aneh untuk membuatnya mengimplementasikanAutoCloseable
...using
tidak sama dengan RAII - dalam satu kasus penelepon khawatir tentang membuang sumber daya, dalam kasus lain callee menanganinya.using
/ coba-dengan-sumber daya tidak sama dengan RAII.using
dan sejenisnya tidak ada di dekat RAII.Java sengaja tidak memiliki objek berbasis tumpukan (alias nilai-objek). Ini diperlukan agar objek secara otomatis dihancurkan pada akhir metode seperti itu.
Karena ini dan fakta bahwa Jawa adalah pengumpulan sampah, finalisasi deterministik lebih-atau-kurang mustahil (mis. Bagaimana jika objek "lokal" saya menjadi rujukan di tempat lain? Kemudian ketika metode ini berakhir, kami tidak ingin merusaknya. ) .
Namun, ini tidak masalah bagi kebanyakan dari kita, karena hampir tidak pernah ada kebutuhan untuk finalisasi deterministik, kecuali ketika berinteraksi dengan sumber daya asli (C ++)!
Mengapa Java tidak memiliki objek berbasis stack?
(Selain primitif ..)
Karena objek berbasis tumpukan memiliki semantik berbeda dari referensi berbasis tumpukan. Bayangkan kode berikut dalam C ++; apa fungsinya?
myObject
objek berbasis stack lokal, copy-constructor dipanggil (jika hasilnya ditugaskan untuk sesuatu).myObject
objek berbasis stack lokal dan kami mengembalikan referensi, hasilnya tidak terdefinisi.myObject
adalah anggota / objek global, copy-constructor dipanggil (jika hasilnya ditugaskan untuk sesuatu).myObject
adalah anggota / objek global dan kami mengembalikan referensi, referensi dikembalikan.myObject
adalah pointer ke objek berbasis stack lokal, hasilnya tidak ditentukan.myObject
adalah penunjuk ke objek anggota / global, penunjuk itu dikembalikan.myObject
pointer ke objek berbasis heap, pointer itu dikembalikan.Sekarang apa yang dilakukan kode yang sama di Java?
myObject
dikembalikan. Tidak masalah jika variabelnya lokal, anggota, atau global; dan tidak ada objek berbasis stack atau case pointer yang perlu dikhawatirkan.Di atas menunjukkan mengapa objek berbasis stack adalah penyebab paling umum kesalahan pemrograman di C ++. Karena itu, desainer Jawa mengambilnya; dan tanpa mereka, tidak ada gunanya menggunakan RAII di Jawa.
sumber
Uraian Anda tentang lubang
using
tidak lengkap. Pertimbangkan masalah berikut:Menurut pendapat saya, tidak memiliki RAII dan GC adalah ide yang buruk. Ketika datang untuk menutup file di Jawa, itu
malloc()
danfree()
di sana.sumber
using
klausa ini merupakan langkah maju yang bagus untuk C # di Jawa. Ini memungkinkan perusakan deterministik dan dengan demikian memperbaiki manajemen sumber daya (ini tidak sebagus RAII seperti yang Anda perlu ingat untuk melakukannya, tetapi itu jelas merupakan ide yang baik).free()
difinally
.IEnumerable
tidak mewarisiIDisposable
, dan ada banyak iterator khusus yang tidak pernah dapat diimplementasikan sebagai hasilnya.Saya cukup tua. Aku pernah ke sana dan melihatnya dan membenturkan kepalaku berkali-kali.
Saya berada di sebuah konferensi di Hursley Park di mana anak-anak IBM memberi tahu kami betapa indahnya bahasa Jawa yang baru ini, hanya ada yang bertanya ... mengapa tidak ada penghancur benda-benda ini. Dia tidak bermaksud hal yang kita tahu sebagai destruktor di C ++, tetapi tidak ada finalis juga (atau ada finalis tetapi mereka pada dasarnya tidak bekerja). Ini jauh di belakang, dan kami memutuskan bahwa Jawa sedikit bahasa mainan pada saat itu.
sekarang mereka menambahkan Finalisers ke spec bahasa dan Java melihat beberapa adopsi.
Tentu saja, nanti semua orang diberitahu untuk tidak menempatkan finalis pada objek mereka karena itu memperlambat GC. (karena harus tidak hanya mengunci tumpukan tetapi memindahkan objek yang akan diselesaikan ke area temp, karena metode ini tidak dapat dipanggil karena GC telah menghentikan aplikasi agar tidak berjalan. Sebaliknya mereka akan dipanggil segera sebelum yang berikutnya Siklus GC) (dan lebih buruk lagi, terkadang finaliser tidak akan pernah dipanggil sama sekali ketika aplikasi dimatikan. Bayangkan tidak ada file handle Anda yang ditutup, selamanya)
Kemudian kami memiliki C #, dan saya ingat forum diskusi di MSDN di mana kami diberitahu betapa indahnya bahasa C # baru ini. Seseorang bertanya mengapa tidak ada finalisasi deterministik dan anak-anak MS mengatakan kepada kami bagaimana kami tidak membutuhkan hal-hal seperti itu, kemudian mengatakan kepada kami bahwa kami perlu mengubah cara kami merancang aplikasi, kemudian memberi tahu kami betapa menakjubkannya GC dan bagaimana semua aplikasi lama kami sampah dan tidak pernah berhasil karena semua referensi melingkar. Kemudian mereka menyerah pada tekanan dan memberi tahu kami bahwa mereka telah menambahkan pola IDispose ini ke spek yang bisa kita gunakan. Saya pikir itu cukup banyak kembali ke manajemen memori manual untuk kami di aplikasi C # pada saat itu.
Tentu saja, anak-anak MS kemudian menemukan bahwa semua yang mereka katakan kepada kami adalah ... well, mereka membuat IDispose sedikit lebih dari sekadar antarmuka standar, dan kemudian menambahkan pernyataan menggunakan. W00t! Mereka menyadari bahwa finalisasi deterministik adalah sesuatu yang hilang dari bahasa. Tentu saja, Anda masih harus ingat untuk meletakkannya di mana-mana, jadi ini masih sedikit manual, tetapi lebih baik.
Jadi mengapa mereka melakukannya ketika mereka bisa menggunakan semantik gaya yang secara otomatis ditempatkan pada setiap blok lingkup sejak awal? Mungkin efisiensi, tetapi saya suka berpikir bahwa mereka tidak menyadarinya. Sama seperti akhirnya mereka menyadari bahwa Anda masih memerlukan pointer pintar di .NET (google SafeHandle) mereka berpikir bahwa GC benar-benar akan menyelesaikan semua masalah. Mereka lupa bahwa suatu objek lebih dari sekadar memori dan bahwa GC terutama dirancang untuk menangani manajemen memori. mereka terjebak dalam gagasan bahwa GC akan menangani ini, dan lupa bahwa Anda meletakkan barang-barang lain di sana, sebuah objek bukan hanya gumpalan memori yang tidak masalah jika Anda tidak menghapusnya untuk sementara waktu.
Tetapi saya juga berpikir bahwa kurangnya metode penyelesaian dalam Java asli memiliki sedikit lebih banyak - bahwa objek yang Anda buat adalah semua tentang memori, dan jika Anda ingin menghapus sesuatu yang lain (seperti pegangan DB atau soket atau apa pun ) maka Anda diharapkan melakukannya secara manual .
Ingat Java dirancang untuk lingkungan tertanam di mana orang terbiasa menulis kode C dengan banyak alokasi manual, jadi tidak memiliki bebas otomatis tidak banyak masalah - mereka tidak pernah melakukannya sebelumnya, jadi mengapa Anda membutuhkannya di Jawa? Masalahnya tidak ada hubungannya dengan utas, atau tumpukan / tumpukan, itu mungkin hanya ada untuk membuat alokasi memori (dan karenanya de-alokasi) sedikit lebih mudah. Secara keseluruhan, pernyataan try / akhirnya mungkin merupakan tempat yang lebih baik untuk menangani sumber daya non-memori.
Jadi IMHO, cara. NET hanya menyalin kelemahan terbesar Jawa adalah kelemahan terbesarnya. .NET seharusnya merupakan C ++ yang lebih baik, bukan Java yang lebih baik.
sumber
Dispose
semua bidang yang ditandai denganusing
arahan, dan menentukan apakahIDisposable.Dispose
harus secara otomatis menyebutnya; (3) arahan mirip denganusing
, tetapi yang hanya akan memanggilDispose
dalam kasus pengecualian; (4) variasiIDisposable
yang akan mengambilException
parameter, dan ...using
jika sesuai; parameternya adalahnull
jikausing
blok keluar secara normal, atau yang lain akan menunjukkan pengecualian apa yang tertunda jika keluar melalui pengecualian. Jika hal-hal seperti itu ada, akan jauh lebih mudah untuk mengelola sumber daya secara efektif dan menghindari kebocoran.Bruce Eckel, penulis "Thinking in Java" dan "Thinking in C ++" dan anggota Komite Standar C ++, berpendapat bahwa, di banyak daerah (tidak hanya RAII), Gosling dan tim Java tidak melakukan pekerjaan rumah.
sumber
Alasan terbaik jauh lebih sederhana daripada sebagian besar jawaban di sini.
Anda tidak dapat meneruskan tumpukan objek yang dialokasikan ke utas lainnya.
Berhentilah dan pikirkan itu. Teruslah berpikir .... Sekarang C ++ tidak memiliki utas ketika semua orang begitu tertarik pada RAII. Bahkan Erlang (tumpukan terpisah per utas) menjadi menjengkelkan ketika Anda melewati terlalu banyak objek di sekitar. C ++ hanya mendapatkan model memori di C ++ 2011; sekarang Anda hampir dapat beralasan tentang concurrency di C ++ tanpa harus merujuk ke "dokumentasi" kompiler Anda.
Java dirancang dari (hampir) hari pertama untuk beberapa utas.
Saya masih memiliki salinan lama "Bahasa Pemrograman C ++" di mana Stroustrup meyakinkan saya bahwa saya tidak akan memerlukan utas.
Alasan menyakitkan kedua adalah untuk menghindari mengiris.
sumber
Di C ++, Anda menggunakan lebih banyak tujuan umum, fitur-fitur tingkat bahasa yang lebih rendah (destruktor secara otomatis dipanggil pada objek berbasis stack) untuk mengimplementasikan yang lebih tinggi (RAII), dan pendekatan ini adalah sesuatu yang tampaknya tidak dimiliki oleh orang C # / Java. terlalu suka. Mereka lebih suka merancang alat tingkat tinggi spesifik untuk kebutuhan spesifik, dan menyediakannya kepada pemrogram yang siap pakai, dibangun ke dalam bahasa. Masalah dengan alat khusus seperti itu adalah bahwa mereka sering tidak mungkin untuk dikustomisasi (sebagian itulah yang membuatnya mudah dipelajari). Ketika membangun dari blok yang lebih kecil, solusi yang lebih baik mungkin muncul seiring waktu, sedangkan jika Anda hanya memiliki konstruksi tingkat tinggi, built-in, ini lebih kecil kemungkinannya.
Jadi ya, saya pikir (saya tidak benar-benar ada ...) itu adalah keputusan yang sadar, dengan tujuan membuat bahasa lebih mudah untuk diambil, tetapi menurut saya, itu adalah keputusan yang buruk. Kemudian lagi, saya umumnya lebih suka C ++ memberikan-programmer-kesempatan-untuk-roll-filosofi mereka sendiri, jadi saya agak bias.
sumber
Anda sudah memanggil kasar setara dengan ini di C # dengan
Dispose
metode. Java juga punyafinalize
. CATATAN: Saya menyadari bahwa finalisasi Java adalah non-deterministik dan berbeda dariDispose
, saya hanya menunjukkan bahwa mereka berdua memiliki metode pembersihan sumber daya bersama dengan GC.Namun C ++ menjadi lebih menyakitkan karena suatu objek harus dihancurkan secara fisik. Dalam bahasa tingkat yang lebih tinggi seperti C # dan Java kita bergantung pada pengumpul sampah untuk membersihkannya ketika tidak ada referensi lagi untuk itu. Tidak ada jaminan bahwa objek DBConnection di C ++ tidak memiliki referensi jahat atau petunjuk untuk itu.
Ya, kode C ++ bisa lebih intuitif untuk dibaca tetapi bisa menjadi mimpi buruk untuk di-debug karena batasan dan batasan yang digunakan bahasa seperti Java menyingkirkan beberapa bug yang lebih menjengkelkan dan sulit serta melindungi pengembang lain dari kesalahan pemula yang baru.
Mungkin turun ke preferensi, beberapa seperti kekuatan tingkat rendah, kontrol dan kemurnian C ++ di mana orang lain seperti saya lebih suka bahasa kotak pasir yang lebih eksplisit.
sumber