Bisakah Anda pengembang C ++ memberi kami penjelasan yang baik tentang apa itu RAII, mengapa itu penting, dan apakah itu mungkin memiliki relevansi dengan bahasa lain atau tidak?
Saya lakukan tahu sedikit. Saya yakin itu singkatan dari "Resource Acquisition is Initialization". Namun, nama itu tidak cocok dengan pemahaman saya (mungkin salah) tentang apa itu RAII: Saya mendapat kesan bahwa RAII adalah cara menginisialisasi objek di tumpukan sedemikian rupa sehingga, ketika variabel-variabel itu keluar dari ruang lingkup, destruktor akan secara otomatis disebut menyebabkan sumber daya dibersihkan.
Jadi mengapa itu tidak disebut "menggunakan tumpukan untuk memicu pembersihan" (UTSTTC :)? Bagaimana Anda pergi dari sana ke "RAII"?
Dan bagaimana Anda bisa membuat sesuatu di tumpukan yang akan menyebabkan pembersihan sesuatu yang hidup di tumpukan? Juga, adakah kasus di mana Anda tidak dapat menggunakan RAII? Apakah Anda pernah berharap untuk mengumpulkan sampah? Setidaknya pengumpul sampah yang dapat Anda gunakan untuk beberapa objek sambil membiarkan yang lain dikelola?
Terima kasih.
sumber
:(
Saya sedang membaca seluruh utas, saat itu, dan bahkan tidak menganggap diri saya seorang pemula C ++!Jawaban:
RAII memberi tahu Anda apa yang harus dilakukan: Dapatkan sumber daya Anda di konstruktor! Saya akan menambahkan: satu sumber daya, satu konstruktor. UTSTTC hanyalah salah satu aplikasi dari itu, RAII lebih dari itu.
Manajemen Sumber Daya menyebalkan. Di sini, sumber daya adalah segala sesuatu yang perlu dibersihkan setelah digunakan. Studi proyek di banyak platform menunjukkan sebagian besar bug terkait dengan manajemen sumber daya - dan ini sangat buruk di Windows (karena banyak jenis objek dan pengalokasi).
Di C ++, pengelolaan sumber daya menjadi sangat rumit karena kombinasi pengecualian dan template (gaya C ++). Untuk mengintip di bawah tenda, lihat GOTW8 ).
C ++ menjamin bahwa destruktor dipanggil jika dan hanya jika konstruktor berhasil. Mengandalkan itu, RAII dapat memecahkan banyak masalah buruk yang mungkin tidak disadari oleh programmer rata-rata. Berikut adalah beberapa contoh di luar "variabel lokal saya akan dihancurkan setiap kali saya kembali".
Mari kita mulai dengan
FileHandle
kelas yang terlalu sederhana yang menggunakan RAII:Jika konstruksi gagal (dengan pengecualian), tidak ada fungsi anggota lain - bahkan destruktornya - yang dipanggil.
RAII menghindari penggunaan objek dalam keadaan tidak valid. itu sudah membuat hidup lebih mudah bahkan sebelum kita menggunakan objeknya.
Sekarang, mari kita lihat objek sementara:
Ada tiga kasus kesalahan yang harus ditangani: tidak ada file yang dapat dibuka, hanya satu file yang dapat dibuka, kedua file dapat dibuka tetapi penyalinan file gagal. Dalam implementasi non-RAII,
Foo
harus menangani ketiga kasus secara eksplisit.RAII melepaskan sumber daya yang diperoleh, meskipun beberapa sumber daya diperoleh dalam satu pernyataan.
Sekarang, mari kita menggabungkan beberapa objek:
Konstruktor
Logger
akan gagal jikaoriginal
konstruktor gagal (karenafilename1
tidak dapat dibuka),duplex
konstruktor gagal (karenafilename2
tidak dapat dibuka), atau penulisan ke file di dalamLogger
badan konstruktor gagal. Dalam salah satu kasus ini,Logger
destruktor tidak akan dipanggil - jadi kita tidak bisa mengandalkanLogger
destruktor untuk melepaskan file. Tetapi jikaoriginal
dibangun, destruktornya akan dipanggil selama pembersihanLogger
konstruktor.RAII menyederhanakan pembersihan setelah konstruksi parsial.
Poin negatif:
Poin negatif? Semua masalah dapat diselesaikan dengan RAII dan petunjuk cerdas ;-)
RAII terkadang sulit digunakan saat Anda membutuhkan akuisisi tertunda, yang mendorong objek gabungan ke heap.
Bayangkan Logger membutuhkan a
SetTargetFile(const char* target)
. Dalam hal ini, pegangan, yang masih perlu menjadi anggotaLogger
, harus berada di heap (misalnya dalam penunjuk cerdas, untuk memicu penghancuran pegangan dengan tepat.)Saya tidak pernah berharap untuk mengumpulkan sampah juga. Ketika saya melakukan C # saya terkadang merasakan momen kebahagiaan karena saya tidak perlu peduli, tetapi lebih dari itu saya merindukan semua mainan keren yang dapat diciptakan melalui kehancuran deterministik. (menggunakan
IDisposable
saja tidak memotongnya.)Saya memiliki satu struktur yang sangat kompleks yang mungkin mendapat manfaat dari GC, di mana petunjuk cerdas "sederhana" akan menyebabkan referensi melingkar di beberapa kelas. Kami menyelesaikannya dengan hati-hati menyeimbangkan petunjuk yang kuat dan yang lemah, tetapi kapan pun kami ingin mengubah sesuatu, kami harus mempelajari bagan hubungan yang besar. GC mungkin lebih baik, tetapi beberapa komponen memiliki sumber daya yang seharusnya dirilis secepatnya.
Catatan pada sampel FileHandle: Itu tidak dimaksudkan sebagai lengkap, hanya sampel - tetapi ternyata salah. Terima kasih Johannes Schaub untuk menunjukkan dan FredOverflow untuk mengubahnya menjadi solusi C ++ 0x yang benar. Seiring waktu, saya telah menyelesaikan pendekatan yang didokumentasikan di sini .
sumber
Foo
memilikiBar
, danBoz
memutasinya, ...Ada jawaban yang sangat bagus di luar sana, jadi saya hanya menambahkan beberapa hal yang terlupakan.
0. RAII adalah tentang ruang lingkup
RAII adalah tentang keduanya:
Yang lain sudah menjawab tentang itu, jadi saya tidak akan menjelaskannya.
1. Saat melakukan coding di Java atau C #, Anda sudah menggunakan RAII ...
Seperti yang dilakukan Monsieur Jourdain dengan prosa, C # dan bahkan orang Java sudah menggunakan RAII, tetapi dengan cara tersembunyi. Misalnya, kode Java berikut (yang ditulis dengan cara yang sama di C # dengan menggantinya
synchronized
denganlock
):... sudah menggunakan RAII: Akuisisi mutex dilakukan di kata kunci (
synchronized
ataulock
), dan pembatalan akuisisi akan dilakukan saat keluar dari cakupan.Sangat alami dalam notasinya sehingga hampir tidak memerlukan penjelasan bahkan bagi orang yang belum pernah mendengar tentang RAII.
Keunggulan C ++ dibandingkan Java dan C # di sini adalah bahwa apa pun dapat dibuat menggunakan RAII. Misalnya, tidak ada build-in langsung yang setara dengan
synchronized
norlock
di C ++, tetapi kita masih bisa memilikinya.Di C ++, itu akan ditulis:
yang dapat dengan mudah ditulis dengan cara Java / C # (menggunakan makro C ++):
2. RAII memiliki kegunaan alternatif
Anda tahu kapan konstruktor akan dipanggil (di deklarasi objek), dan Anda tahu kapan destruktor yang sesuai akan dipanggil (di pintu keluar cakupan), sehingga Anda dapat menulis kode yang hampir ajaib dengan hanya satu baris. Selamat datang di negeri ajaib C ++ (setidaknya, dari sudut pandang pengembang C ++).
Misalnya, Anda dapat menulis objek counter (saya biarkan sebagai latihan) dan menggunakannya hanya dengan mendeklarasikan variabelnya, seperti objek kunci di atas digunakan:
yang tentu saja, dapat ditulis, sekali lagi, cara Java / C # menggunakan makro:
3. Mengapa C ++ kurang
finally
?The
finally
klausul digunakan dalam C # / Java untuk menangani pembuangan sumber daya dalam kasus lingkup keluar (baik melaluireturn
atau pengecualian dilemparkan).Pembaca spesifikasi yang cerdik akan menyadari bahwa C ++ tidak memiliki klausa akhirnya. Dan ini bukan kesalahan, karena C ++ tidak membutuhkannya, karena RAII sudah menangani pembuangan sumber daya. (Dan percayalah, menulis destruktor C ++ jauh lebih mudah daripada menulis klausa akhir Java yang benar, atau bahkan metode Buang C # yang benar).
Meski begitu, terkadang,
finally
klausul akan tetap keren. Bisakah kita melakukannya di C ++? Ya kita bisa! Dan lagi dengan penggunaan RAII secara bergantian.Kesimpulan: RAII lebih dari sekedar filosofi dalam C ++: Ini C ++
Ketika Anda mencapai beberapa tingkat pengalaman dalam C ++, Anda mulai berpikir dalam istilah RAII , dalam istilah eksekusi otomatis konstruktor dan destruktor .
Anda mulai berpikir dalam lingkup cakupan ,
{
dan}
karakter dan menjadi salah satu yang paling penting dalam kode Anda.Dan hampir semuanya cocok dalam hal RAII: pengecualian keamanan, mutex, koneksi database, permintaan database, koneksi server, jam, pegangan OS, dll., Dan terakhir, namun tidak kalah pentingnya, memori.
Bagian database tidak dapat diabaikan, karena, jika Anda menerima pembayaran, Anda bahkan dapat menulis dalam gaya " pemrograman transaksional ", mengeksekusi baris dan baris kode sampai memutuskan, pada akhirnya, jika Anda ingin melakukan semua perubahan , atau, jika tidak memungkinkan, mengembalikan semua perubahan (selama setiap baris memenuhi setidaknya Jaminan Pengecualian Kuat). (lihat bagian kedua dari artikel Herb's Sutter ini untuk pemrograman transaksional).
Dan seperti teka-teki, semuanya cocok.
RAII adalah bagian dari C ++, C ++ tidak bisa menjadi C ++ tanpanya.
Ini menjelaskan mengapa developer C ++ berpengalaman begitu terpikat dengan RAII, dan mengapa RAII menjadi hal pertama yang mereka telusuri saat mencoba bahasa lain.
Dan ini menjelaskan mengapa Pengumpul Sampah, meskipun merupakan bagian dari teknologi yang luar biasa, tidak begitu mengesankan dari sudut pandang pengembang C ++:
sumber
Silahkan lihat:
Apakah pemrogram bahasa lain, selain C ++, menggunakan, mengetahui, atau memahami RAII?
RAII dan smart pointer dalam C ++
Apakah C ++ mendukung blok 'akhirnya'? (Dan apa itu 'RAII' yang terus saya dengar?)
RAII vs. pengecualian
dll ..
sumber
RAII menggunakan semantik destruktor C ++ untuk mengelola sumber daya. Misalnya, pertimbangkan penunjuk cerdas. Anda memiliki konstruktor berparameter dari pointer yang menginisialisasi pointer ini dengan alamat objek. Anda mengalokasikan pointer pada tumpukan:
Ketika smart pointer keluar dari ruang lingkup destruktor dari kelas pointer menghapus objek yang terhubung. Pointer dialokasikan stack dan objek - dialokasikan heap.
Ada kasus-kasus tertentu ketika RAII tidak membantu. Misalnya, jika Anda menggunakan petunjuk cerdas penghitungan referensi (seperti boost :: shared_ptr) dan membuat struktur seperti grafik dengan siklus, Anda berisiko mengalami kebocoran memori karena objek dalam siklus akan mencegah satu sama lain untuk dilepaskan. Pengumpulan sampah akan membantu melawan ini.
sumber
Saya ingin menjelaskannya sedikit lebih kuat dari tanggapan sebelumnya.
RAII, Resource Acquisition Is Initialization berarti bahwa semua sumber daya yang diperoleh harus diperoleh dalam konteks inisialisasi suatu objek. Ini melarang akuisisi sumber daya secara "telanjang". Alasannya adalah bahwa pembersihan di C ++ bekerja pada basis objek, bukan basis pemanggilan fungsi. Karenanya, semua pembersihan harus dilakukan oleh objek, bukan pemanggilan fungsi. Dalam pengertian ini C ++ lebih berorientasi objek daripada misalnya Java. Pembersihan Java didasarkan pada pemanggilan fungsi dalam
finally
klausa.sumber
Saya setuju dengan cpitis. Tetapi ingin menambahkan bahwa sumber daya dapat berupa apa saja, bukan hanya memori. Sumber daya bisa berupa file, bagian penting, utas atau koneksi database.
Ini disebut Resource Acquisition Is Initialization karena sumber daya diperoleh ketika objek yang mengontrol sumber daya dibuat, Jika konstruktor gagal (yaitu karena pengecualian) sumber daya tidak diperoleh. Kemudian setelah objek keluar dari ruang lingkup, sumber daya dilepaskan. c ++ menjamin bahwa semua objek di tumpukan yang telah berhasil dibangun akan dihancurkan (ini termasuk konstruktor kelas dasar dan anggota bahkan jika konstruktor kelas super gagal).
Alasan di balik RAII adalah untuk membuat pengecualian akuisisi sumber daya aman. Bahwa semua sumber daya yang diperoleh dilepaskan dengan benar di mana pun pengecualian terjadi. Namun ini bergantung pada kualitas kelas yang memperoleh sumber daya (ini harus pengecualian aman dan ini sulit).
sumber
Masalah dengan pengumpulan sampah adalah Anda kehilangan kehancuran deterministik yang penting bagi RAII. Setelah variabel keluar dari ruang lingkup, terserah pengumpul sampah kapan objek akan diklaim kembali. Sumber daya yang dipegang oleh objek akan terus ditahan hingga destruktor dipanggil.
sumber
RAII berasal dari Resource Allocation Is Initialization. Pada dasarnya, ini berarti bahwa ketika konstruktor menyelesaikan eksekusi, objek yang dibangun sepenuhnya diinisialisasi dan siap digunakan. Ini juga menyiratkan bahwa destruktor akan melepaskan sumber daya apa pun (misalnya memori, sumber daya OS) yang dimiliki oleh objek.
Dibandingkan dengan bahasa / teknologi yang dikumpulkan sampah (misalnya Java, .NET), C ++ memungkinkan kontrol penuh atas kehidupan suatu objek. Untuk objek yang dialokasikan tumpukan, Anda akan tahu kapan destruktor objek akan dipanggil (saat eksekusi keluar dari cakupan), hal yang tidak benar-benar dikontrol dalam kasus pengumpulan sampah. Bahkan menggunakan smart pointer di C ++ (mis. Boost :: shared_ptr), Anda akan tahu bahwa jika tidak ada referensi ke objek yang dituju, destruktor dari objek itu akan dipanggil.
sumber
Ketika sebuah instance dari int_buffer muncul, ia harus memiliki ukuran, dan itu akan mengalokasikan memori yang diperlukan. Ketika keluar dari ruang lingkup, itu destruktor yang dipanggil. Ini sangat berguna untuk hal-hal seperti objek sinkronisasi. Mempertimbangkan
Tidak terlalu.
Tidak pernah. Pengumpulan sampah hanya menyelesaikan sebagian kecil dari pengelolaan sumber daya dinamis.
sumber
Sudah ada banyak jawaban bagus di sini, tetapi saya hanya ingin menambahkan:
Penjelasan sederhana tentang RAII adalah bahwa, dalam C ++, objek yang dialokasikan di tumpukan akan dimusnahkan setiap kali berada di luar cakupan. Itu berarti, perusak objek akan dipanggil dan dapat melakukan semua pembersihan yang diperlukan.
Artinya, jika sebuah objek dibuat tanpa "baru", tidak diperlukan "penghapusan". Dan ini juga merupakan ide di balik "penunjuk cerdas" - mereka berada di tumpukan, dan pada dasarnya membungkus objek berbasis tumpukan.
sumber
RAII adalah singkatan dari Resource Acquisition Is Initialization.
Teknik ini sangat unik untuk C ++ karena dukungan mereka untuk Constructors & Destructors & hampir secara otomatis konstruktor yang cocok dengan argumen yang diteruskan atau dalam kasus terburuk konstruktor default disebut & destructors jika penjelasan yang diberikan disebut sebaliknya yang default yang ditambahkan oleh compiler C ++ dipanggil jika Anda tidak menulis destruktor secara eksplisit untuk kelas C ++. Ini hanya terjadi untuk objek C ++ yang dikelola secara otomatis - artinya tidak menggunakan penyimpanan gratis (memori dialokasikan / dialokasikan menggunakan operator baru [] / delete, delete [] C ++ baru).
Teknik RAII memanfaatkan fitur objek yang dikelola otomatis ini untuk menangani objek yang dibuat di heap / free-store dengan secara eksplisit meminta lebih banyak memori menggunakan new / new [], yang harus dimusnahkan secara eksplisit dengan memanggil delete / delete [] . Kelas objek yang dikelola otomatis akan membungkus objek lain yang dibuat pada memori heap / penyimpanan bebas ini. Oleh karena itu, ketika konstruktor objek yang dikelola otomatis dijalankan, objek yang dibungkus dibuat pada memori heap / free-store & saat pegangan objek yang dikelola otomatis keluar dari ruang lingkup, destruktor dari objek yang dikelola otomatis itu dipanggil secara otomatis di mana objek dihancurkan menggunakan delete. Dengan konsep OOP, jika Anda membungkus objek tersebut di dalam kelas lain dalam lingkup privat, Anda tidak akan memiliki akses ke kelas yang dibungkus anggota & metode & inilah alasan mengapa smart pointer (alias menangani kelas) dirancang untuk. Pointer cerdas ini mengekspos objek yang dibungkus sebagai objek yang diketik ke dunia luar & di sana dengan memungkinkan untuk memanggil anggota / metode apa pun yang terdiri dari objek memori yang terekspos. Perhatikan bahwa petunjuk cerdas memiliki berbagai rasa berdasarkan kebutuhan yang berbeda. Anda harus mengacu pada pemrograman C ++ Modern oleh Andrei Alexandrescu atau meningkatkan implementasi / dokumentasi shared_ptr.hpp library (www.boostorg) untuk mempelajari lebih lanjut tentangnya. Semoga ini bisa membantu Anda untuk memahami RAII. Anda harus mengacu pada pemrograman C ++ Modern oleh Andrei Alexandrescu atau meningkatkan implementasi / dokumentasi shared_ptr.hpp library (www.boostorg) untuk mempelajari lebih lanjut tentangnya. Semoga ini bisa membantu Anda untuk memahami RAII. Anda harus mengacu pada pemrograman C ++ Modern oleh Andrei Alexandrescu atau meningkatkan implementasi / dokumentasi shared_ptr.hpp library (www.boostorg) untuk mempelajari lebih lanjut tentangnya. Semoga ini bisa membantu Anda untuk memahami RAII.
sumber