Apakah C ++ mendukung ' akhirnya ' blok?
Apa idiom RAII ?
Apa perbedaan antara idiom RAII C ++ dan pernyataan C # yang menggunakan ?
Apakah C ++ mendukung ' akhirnya ' blok?
Apa idiom RAII ?
Apa perbedaan antara idiom RAII C ++ dan pernyataan C # yang menggunakan ?
Jawaban:
Tidak, C ++ tidak mendukung blok 'akhirnya'. Alasannya adalah bahwa C ++ sebaliknya mendukung RAII: "Akuisisi Sumber Daya Adalah Inisialisasi" - nama yang buruk † untuk konsep yang sangat berguna.
Idenya adalah bahwa destruktor suatu objek bertanggung jawab untuk membebaskan sumber daya. Ketika objek memiliki durasi penyimpanan otomatis, destruktor objek akan dipanggil ketika blok tempat ia dibuat keluar - bahkan ketika blok itu keluar di hadapan pengecualian. Inilah penjelasan Bjarne Stroustrup tentang topik tersebut.
Penggunaan umum untuk RAII adalah mengunci mutex:
RAII juga menyederhanakan menggunakan objek sebagai anggota kelas lain. Ketika kelas pemilik 'dihancurkan, sumber daya yang dikelola oleh kelas RAII dilepaskan karena destruktor untuk kelas yang dikelola RAII dipanggil sebagai hasilnya. Ini berarti bahwa ketika Anda menggunakan RAII untuk semua anggota di kelas yang mengelola sumber daya, Anda bisa lolos dengan menggunakan destruktor yang sangat sederhana, bahkan mungkin default, untuk kelas pemilik karena tidak perlu mengelola masa pakai sumber daya anggotanya secara manual. . (Terima kasih kepada Mike B untuk menunjukkan ini.)
Bagi mereka yang akrab dengan C # atau VB.NET, Anda mungkin mengakui bahwa RAII mirip dengan kehancuran deterministik .NET menggunakan pernyataan IDisposable dan 'using' . Memang, kedua metode ini sangat mirip. Perbedaan utama adalah bahwa RAII akan secara pasti melepaskan semua jenis sumber daya - termasuk memori. Saat menerapkan IDisposable di .NET (bahkan bahasa .NET C ++ / CLI), sumber daya akan dirilis secara deterministik kecuali untuk memori. Di .NET, memori tidak dilepaskan secara deterministik; memori hanya dilepaskan selama siklus pengumpulan sampah.
† Beberapa orang percaya bahwa "Penghancuran adalah Pelepasan Sumber Daya" adalah nama yang lebih akurat untuk idiom RAII.
sumber
Dalam C ++ akhirnya TIDAK diperlukan karena RAII.
RAII memindahkan tanggung jawab keselamatan pengecualian dari pengguna objek ke perancang (dan pelaksana) objek. Saya berpendapat ini adalah tempat yang tepat karena Anda hanya perlu mendapatkan pengecualian keselamatan yang benar sekali (dalam desain / implementasi). Dengan menggunakan akhirnya Anda harus mendapatkan pengecualian keselamatan yang benar setiap kali Anda menggunakan objek.
Juga IMO kodenya terlihat lebih rapi (lihat di bawah).
Contoh:
Objek basis data. Untuk memastikan koneksi DB digunakan, itu harus dibuka dan ditutup. Dengan menggunakan RAII ini dapat dilakukan di konstruktor / destruktor.
C ++ Suka RAII
Penggunaan RAII membuat menggunakan objek DB dengan benar sangat mudah. Objek DB akan benar menutup dirinya dengan menggunakan destruktor tidak peduli bagaimana kami mencoba dan menyalahgunakannya.
Akhirnya Java
Saat menggunakan akhirnya penggunaan yang benar dari objek didelegasikan kepada pengguna objek. yaitu adalah tanggung jawab pengguna objek untuk secara benar menutup koneksi DB secara eksplisit. Sekarang Anda dapat berargumen bahwa ini dapat dilakukan di finaliser, tetapi sumber daya mungkin memiliki ketersediaan terbatas atau kendala lain dan dengan demikian Anda umumnya ingin mengontrol pelepasan objek dan tidak bergantung pada perilaku non deterministik dari pengumpul sampah.
Ini juga contoh sederhana.
Ketika Anda memiliki banyak sumber daya yang perlu dirilis kode dapat menjadi rumit.
Analisis yang lebih terperinci dapat ditemukan di sini: http://accu.org/index.php/journals/236
sumber
// Make sure not to throw exception if one is already propagating.
Penting bagi penghancur C ++ untuk tidak membuang pengecualian juga karena alasan ini.RAII biasanya lebih baik, tetapi Anda dapat dengan mudah memiliki semantik terakhir di C ++. Menggunakan sejumlah kecil kode.
Selain itu, Panduan Inti C ++ akhirnya memberi.
Berikut adalah tautan ke implementasi Microsoft GSL dan tautan ke penerapan Martin Moene
Bjarne Stroustrup beberapa kali mengatakan bahwa segala sesuatu yang ada di GSL artinya akan masuk standar pada akhirnya. Jadi itu harus menjadi cara masa depan-bukti untuk menggunakan akhirnya .
Anda dapat dengan mudah menerapkan diri sendiri jika ingin, lanjutkan membaca.
Dalam C ++ 11 RAII dan lambdas memungkinkan untuk membuat jenderal akhirnya:
contoh penggunaan:
hasilnya adalah:
Secara pribadi saya menggunakan ini beberapa kali untuk memastikan untuk menutup deskriptor file POSIX dalam program C ++.
Memiliki kelas nyata yang mengelola sumber daya dan menghindari segala jenis kebocoran biasanya lebih baik, tetapi ini akhirnya berguna dalam kasus di mana membuat kelas terdengar seperti pembunuhan berlebihan.
Selain itu, aku seperti itu lebih baik daripada bahasa lain akhirnya karena jika digunakan secara alami Anda menulis kode penutupan terdekat kode pembuka (dalam contoh saya baru dan delete ) dan kehancuran berikut konstruksi dalam rangka LIFO seperti biasa di C ++. Satu-satunya downside adalah bahwa Anda mendapatkan variabel otomatis Anda tidak benar-benar menggunakan dan sintaks lambda membuatnya sedikit bising (dalam contoh saya di baris keempat hanya kata akhirnya dan {} -block di sebelah kanan bermakna, the sisanya pada dasarnya berisik).
Contoh lain:
Anggota yang dinonaktifkan berguna jika yang terakhir harus dipanggil hanya jika terjadi kegagalan. Misalnya, Anda harus menyalin objek dalam tiga wadah berbeda, Anda dapat mengatur akhirnya untuk membatalkan setiap salinan dan menonaktifkan setelah semua salinan berhasil. Melakukan hal itu, jika kehancuran tidak dapat terjadi, Anda memastikan jaminan yang kuat.
nonaktifkan contoh:
Jika Anda tidak dapat menggunakan C ++ 11 Anda masih dapat memiliki akhirnya , tetapi kode menjadi sedikit lebih panjang lebar. Cukup tentukan struct dengan hanya konstruktor dan destruktor, konstruktor mengambil referensi untuk apa pun yang diperlukan dan destruktor melakukan tindakan yang Anda butuhkan. Ini pada dasarnya adalah apa yang dilakukan lambda, dilakukan secara manual.
sumber
FinalAction
pada dasarnya sama denganScopeGuard
idiom populer , hanya dengan nama yang berbeda.Selain memudahkan pembersihan dengan objek berbasis tumpukan, RAII juga berguna karena pembersihan 'otomatis' yang sama terjadi ketika objek tersebut adalah anggota kelas lain. Ketika kelas pemilik dihancurkan, sumber daya yang dikelola oleh kelas RAII akan dibersihkan karena dtor untuk kelas itu dipanggil sebagai hasilnya.
Ini berarti bahwa ketika Anda mencapai RAII nirwana dan semua anggota di kelas menggunakan RAII (seperti smart pointer), Anda bisa lolos dengan dtor yang sangat sederhana (bahkan mungkin default) untuk kelas pemilik karena tidak perlu mengelola secara manual masa hidup sumber daya anggota.
sumber
Sebenarnya, bahasa berdasarkan pengumpul Sampah perlu "akhirnya" lebih banyak. Seorang pengumpul sampah tidak menghancurkan objek Anda secara tepat waktu, sehingga tidak dapat diandalkan untuk membersihkan masalah yang berhubungan dengan non-memori dengan benar.
Dalam hal data yang dialokasikan secara dinamis, banyak yang akan berpendapat bahwa Anda harus menggunakan smart-pointer.
Namun...
Sayangnya ini adalah kejatuhannya sendiri. Kebiasaan pemrograman C lama sangat sulit. Saat Anda menggunakan perpustakaan yang ditulis dalam gaya C atau sangat C, RAII tidak akan pernah digunakan. Pendek menulis ulang seluruh API front-end, itu hanya apa yang harus Anda kerjakan. Maka kurangnya "akhirnya" benar-benar menggigit.
sumber
CleanupFailedException
. Apakah ada cara yang masuk akal untuk mencapai hasil seperti itu menggunakan RAII?SomeObject.DoSomething()
metode dan ingin tahu apakah itu (1) berhasil, (2) gagal tanpa efek samping , (3) gagal dengan efek samping pemanggil siap untuk mengatasinya , atau (4) gagal dengan efek samping pemanggil tidak dapat mengatasinya. Hanya penelepon yang akan tahu situasi apa yang bisa dan tidak bisa diatasi; apa yang penelepon butuhkan adalah cara untuk mengetahui apa situasinya. Sayang sekali tidak ada mekanisme standar untuk memasok informasi paling penting tentang pengecualian.Lain "akhirnya" emulasi blok menggunakan fungsi C ++ 11 lambda
Semoga kompiler akan mengoptimalkan kode di atas.
Sekarang kita dapat menulis kode seperti ini:
Jika mau, Anda dapat membungkus idiom ini menjadi makro "coba - akhirnya":
Sekarang "akhirnya" blok tersedia di C ++ 11:
Secara pribadi saya tidak suka "makro" versi "akhirnya" idiom dan lebih suka menggunakan fungsi "with_finally" murni meskipun sintaks lebih besar dalam kasus itu.
Anda dapat menguji kode di atas di sini: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PS
Jika Anda akhirnya perlu memblokir kode Anda, maka penjaga ruang lingkup atau ON_FINALLY / ON_EXCEPTION makro mungkin akan lebih sesuai dengan kebutuhan Anda.
Berikut adalah contoh singkat penggunaan ON_FINALLY / ON_EXCEPTION:
sumber
Maaf telah menggali utas lama seperti itu, tetapi ada kesalahan besar pada alasan berikut:
Lebih sering daripada tidak, Anda harus berurusan dengan objek yang dialokasikan secara dinamis, jumlah objek dinamis, dll. Dalam blok percobaan, beberapa kode dapat membuat banyak objek (berapa banyak yang ditentukan saat runtime) dan menyimpan pointer ke mereka dalam daftar. Sekarang, ini bukan skenario yang eksotis, tetapi sangat umum. Dalam hal ini, Anda ingin menulis hal-hal seperti
Tentu saja daftar itu sendiri akan dihancurkan ketika keluar dari ruang lingkup, tetapi itu tidak akan membersihkan objek sementara yang telah Anda buat.
Sebaliknya, Anda harus menempuh rute yang buruk:
Juga: mengapa bahasa yang dikelola bahkan menyediakan blok terakhir meskipun sumber daya secara otomatis dialokasikan oleh pengumpul sampah?
Petunjuk: masih ada lagi yang bisa Anda lakukan dengan "akhirnya" daripada hanya alokasi memori.
sumber
new
tidak mengembalikan NULL, sebagai gantinya melempar pengecualianstd::shared_ptr
danstd::unique_ptr
langsung di stdlib.FWIW, Microsoft Visual C ++ tidak mendukung coba, akhirnya dan secara historis telah digunakan dalam aplikasi MFC sebagai metode menangkap pengecualian serius yang jika tidak akan mengakibatkan crash. Sebagai contoh;
Saya telah menggunakan ini di masa lalu untuk melakukan hal-hal seperti menyimpan cadangan file yang terbuka sebelum keluar. Pengaturan debug JIT tertentu akan merusak mekanisme ini.
sumber
Seperti yang ditunjukkan dalam jawaban lain, C ++ dapat mendukung
finally
fungsionalitas seperti. Implementasi fungsi ini yang mungkin paling dekat dengan menjadi bagian dari bahasa standar adalah yang menyertai Pedoman Inti C ++ , seperangkat praktik terbaik untuk menggunakan C ++ yang diedit oleh Bjarne Stoustrup dan Herb Sutter. Sebuah implementasifinally
merupakan bagian dari Perpustakaan Dukungan Pedoman (GSL). Sepanjang Panduan, penggunaanfinally
direkomendasikan ketika berhadapan dengan antarmuka gaya lama, dan itu juga memiliki pedoman sendiri, berjudul Gunakan objek final_action untuk mengekspresikan pembersihan jika tidak ada sumber daya pegangan yang tersedia .Jadi, tidak hanya dukungan C ++
finally
, sebenarnya disarankan untuk menggunakannya dalam banyak kasus penggunaan umum.Contoh penggunaan implementasi GSL akan terlihat seperti:
Implementasi dan penggunaan GSL sangat mirip dengan yang ada di jawaban Paolo.Bolzoni . Salah satu perbedaannya adalah bahwa objek yang dibuat
gsl::finally()
tidak memilikidisable()
panggilan. Jika Anda memerlukan fungsionalitas itu (katakanlah, untuk mengembalikan sumber daya setelah dirakit dan tidak ada pengecualian yang pasti terjadi), Anda mungkin lebih memilih implementasi Paolo. Jika tidak, menggunakan GSL sedekat mungkin dengan menggunakan fitur standar seperti yang akan Anda dapatkan.sumber
Tidak juga, tetapi Anda dapat meniru mereka sampai batas tertentu, misalnya:
Perhatikan bahwa blok-terakhir itu sendiri mungkin melemparkan pengecualian sebelum pengecualian asli dilemparkan kembali, sehingga membuang pengecualian asli. Ini adalah perilaku yang sama persis seperti di blok akhirnya Java. Selain itu, Anda tidak dapat menggunakan
return
di dalam blok coba & tangkap.sumber
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
blok.Aku datang dengan
finally
makro yang dapat digunakan hampir seperti ¹ yangfinally
kata kunci di Jawa; itu menggunakanstd::exception_ptr
dan teman-teman, fungsi lambda danstd::promise
, sehingga membutuhkanC++11
atau di atas; itu juga menggunakan pernyataan ekspresi ekstensi GCC, yang juga didukung oleh dentang.PERINGATAN : versi sebelumnya dari jawaban ini menggunakan implementasi konsep yang berbeda dengan lebih banyak keterbatasan.
Pertama, mari kita mendefinisikan kelas pembantu.
Lalu ada makro yang sebenarnya.
Dapat digunakan seperti ini:
Penggunaan
std::promise
membuatnya sangat mudah untuk diimplementasikan, tetapi mungkin juga memperkenalkan sedikit overhead yang tidak dibutuhkan yang dapat dihindari dengan menerapkan kembali hanya fungsi yang diperlukan sajastd::promise
.¹ CAVEAT: ada beberapa hal yang tidak berfungsi seperti versi java
finally
. Dari atas kepala saya:break
pernyataan dari dalamtry
dancatch()
's blok, karena mereka hidup dalam fungsi lambda;catch()
blok setelahtry
: itu persyaratan C ++;try
dancatch()'s
blok, kompilasi akan gagal karenafinally
makro akan diperluas ke kode yang ingin mengembalikan avoid
. Ini bisa, err, dibatalkan dengan memiliki semacamfinally_noreturn
makro.Semua dalam semua, saya tidak tahu apakah saya akan pernah menggunakan barang ini sendiri, tapi itu menyenangkan bermain dengannya. :)
sumber
catch(xxx) {}
blok yang mustahil di awalfinally
makro, di mana xxx adalah tipe palsu hanya untuk keperluan memiliki setidaknya satu blok tangkapan.catch(...)
, bukan?xxx
dalam ruang nama pribadi yang tidak akan pernah digunakan.Saya memiliki kasus penggunaan di mana saya pikir
finally
harus menjadi bagian yang dapat diterima dari bahasa C ++ 11, karena saya pikir lebih mudah untuk membaca dari sudut pandang aliran. Case use saya adalah rantai utas konsumen / produsen, di mana sentinelnullptr
dikirim pada akhir proses untuk mematikan semua utas.Jika C ++ mendukungnya, Anda ingin kode Anda terlihat seperti ini:
Saya pikir ini lebih logis bahwa menempatkan akhirnya deklarasi Anda di awal loop, karena itu terjadi setelah loop telah keluar ... tapi itu angan-angan karena kita tidak bisa melakukannya di C ++. Perhatikan bahwa antrian
downstream
terhubung ke utas lain, sehingga Anda tidak dapat memasukkan sentinel kepush(nullptr)
dalam destruktordownstream
karena tidak dapat dihancurkan pada saat ini ... ia harus tetap hidup sampai utas lainnya menerimanullptr
.Jadi, inilah cara menggunakan kelas RAII dengan lambda untuk melakukan hal yang sama:
dan inilah cara Anda menggunakannya:
sumber
Seperti yang banyak orang nyatakan, solusinya adalah menggunakan fitur C ++ 11 untuk menghindari blok yang terakhir. Salah satu fiturnya adalah
unique_ptr
.Inilah jawaban Mephane yang ditulis menggunakan pola RAII.
Beberapa pengantar untuk menggunakan unique_ptr dengan wadah C ++ Standard Library ada di sini
sumber
Saya ingin memberikan alternatif.
Jika Anda ingin akhirnya blok dipanggil selalu, cukup letakkan setelah block catch terakhir (yang mungkin seharusnya
catch( ... )
untuk menangkap pengecualian tidak dikenal)Jika Anda ingin akhirnya memblokir sebagai hal terakhir yang harus dilakukan ketika ada pengecualian yang dilemparkan, Anda dapat menggunakan variabel lokal boolean - sebelum menjalankan Anda mengaturnya ke false dan menempatkan tugas yang benar di akhir blok coba, kemudian setelah menangkap blok periksa untuk variabel nilai:
sumber
Saya juga berpikir bahwa RIIA bukan pengganti yang sepenuhnya berguna untuk penanganan pengecualian dan akhirnya. BTW, saya juga berpikir RIIA adalah nama yang buruk di sekitar. Saya menyebut jenis kelas ini sebagai 'petugas kebersihan' dan menggunakannya sebagai BANYAK. 95% dari waktu mereka tidak menginisialisasi atau memperoleh sumber daya, mereka menerapkan beberapa perubahan berdasarkan cakupan, atau mengambil sesuatu yang sudah disiapkan dan memastikan itu dihancurkan. Ini menjadi internet resmi yang terobsesi dengan nama pola yang saya gunakan untuk bahkan menyarankan nama saya mungkin lebih baik.
Saya hanya berpikir itu tidak masuk akal untuk mengharuskan bahwa setiap pengaturan rumit dari beberapa hal ad hoc harus memiliki kelas tertulis untuk menampungnya untuk menghindari komplikasi ketika membersihkan semuanya kembali dalam menghadapi kebutuhan untuk menangkap beberapa jenis pengecualian jika terjadi kesalahan dalam proses. Ini akan menyebabkan banyak kelas ad hoc yang tidak diperlukan jika tidak sebaliknya.
Ya itu baik untuk kelas yang dirancang untuk mengelola sumber daya tertentu, atau yang generik yang dirancang untuk menangani serangkaian sumber daya yang serupa. Tetapi, bahkan jika semua hal yang terlibat memiliki pembungkus seperti itu, koordinasi pembersihan mungkin tidak hanya sederhana dalam permintaan terbalik dari para penghancur.
Saya pikir itu masuk akal untuk C ++ untuk akhirnya. Maksudku, ya ampun, begitu banyak bit dan bobs telah terpaku padanya selama beberapa dekade terakhir sehingga tampaknya orang-orang aneh tiba-tiba menjadi konservatif atas sesuatu seperti akhirnya yang bisa sangat berguna dan mungkin tidak ada yang rumit seperti beberapa hal lain yang telah menambahkan (meskipun itu hanya dugaan di pihak saya.)
sumber
sumber
finally
tidak dilakukan.