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:
// A class with implements RAII
class lock
{
mutex &m_;
public:
lock(mutex &m)
: m_(m)
{
m.acquire();
}
~lock()
{
m_.release();
}
};
// A class which uses 'mutex' and 'lock' objects
class foo
{
mutex mutex_; // mutex for locking 'foo' object
public:
void bar()
{
lock scopeLock(mutex_); // lock object.
foobar(); // an operation which may throw an exception
// scopeLock will be destructed even if an exception
// occurs, which will release the mutex and allow
// other functions to lock the object and run.
}
};
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.
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.
void someFunc()
{
DB db("DBDesciptionString");
// Use the db object.
} // db goes out of scope and destructor closes the connection.
// This happens even in the presence of exceptions.
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.
void someFunc()
{
DB db = new DB("DBDesciptionString");
try
{
// Use the db object.
}
finally
{
// Can not rely on finaliser.
// So we must explicitly close the connection.
try
{
db.close();
}
catch(Throwable e)
{
/* Ignore */
// Make sure not to throw exception if one is already propagating.
}
}
}
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
// 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:
namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
FinalAction(F f) : clean_{f} {}
~FinalAction() { if(enabled_) clean_(); }
void disable() { enabled_ = false; };
private:
F clean_;
bool enabled_{true}; }; }
template <typename F>
detail::FinalAction<F> finally(F f) {
return detail::FinalAction<F>(f); }
contoh penggunaan:
#include <iostream>
int main() {
int* a = new int;
auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; });
std::cout << "doing something ...\n"; }
hasilnya adalah:
doing something...
leaving the block, deleting a!
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:
[...]
auto precision = std::cout.precision();
auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
std::cout << std::setprecision(3);
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:
//strong guarantee
void copy_to_all(BIGobj const& a) {
first_.push_back(a);
auto undo_first_push = finally([first_&] { first_.pop_back(); });
second_.push_back(a);
auto undo_second_push = finally([second_&] { second_.pop_back(); });
third_.push_back(a);
//no necessary, put just to make easier to add containers in the future
auto undo_third_push = finally([third_&] { third_.pop_back(); });
undo_first_push.disable();
undo_second_push.disable();
undo_third_push.disable(); }
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.
#include <iostream>
int main() {
int* a = new int;
struct Delete_a_t {
Delete_a_t(int* p) : p_(p) {}
~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
int* p_;
} delete_a(a);
std::cout << "doing something ...\n"; }
FinalAction
pada dasarnya sama dengan ScopeGuard
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.
mengapa bahkan bahasa yang dikelola memberikan blok akhirnya meskipun sumber daya secara otomatis dialokasikan oleh pengumpul sampah?
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...
RAII memindahkan tanggung jawab keselamatan pengecualian dari pengguna objek ke perancang
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.
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
template <typename TCode, typename TFinallyCode>
inline void with_finally(const TCode &code, const TFinallyCode &finally_code)
{
try
{
code();
}
catch (...)
{
try
{
finally_code();
}
catch (...) // Maybe stupid check that finally_code mustn't throw.
{
std::terminate();
}
throw;
}
finally_code();
}
Semoga kompiler akan mengoptimalkan kode di atas.
Sekarang kita dapat menulis kode seperti ini:
with_finally(
[&]()
{
try
{
// Doing some stuff that may throw an exception
}
catch (const exception1 &)
{
// Handling first class of exceptions
}
catch (const exception2 &)
{
// Handling another class of exceptions
}
// Some classes of exceptions can be still unhandled
},
[&]() // finally
{
// This code will be executed in all three cases:
// 1) exception was not thrown at all
// 2) exception was handled by one of the "catch" blocks above
// 3) exception was not handled by any of the "catch" block above
}
);
Jika mau, Anda dapat membungkus idiom ini menjadi makro "coba - akhirnya":
// Please never throw exception below. It is needed to avoid a compilation error
// in the case when we use "begin_try ... finally" without any "catch" block.
class never_thrown_exception {};
#define begin_try with_finally([&](){ try
#define finally catch(never_thrown_exception){throw;} },[&]()
#define end_try ) // sorry for "pascalish" style :(
Sekarang "akhirnya" blok tersedia di C ++ 11:
begin_try
{
// A code that may throw
}
catch (const some_exception &)
{
// Handling some exceptions
}
finally
{
// A code that is always executed
}
end_try; // Sorry again for this ugly thing
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:
void function(std::vector<const char*> &vector)
{
int *arr1 = (int*)malloc(800*sizeof(int));
if (!arr1) { throw "cannot malloc arr1"; }
ON_FINALLY({ free(arr1); });
int *arr2 = (int*)malloc(900*sizeof(int));
if (!arr2) { throw "cannot malloc arr2"; }
ON_FINALLY({ free(arr2); });
vector.push_back("good");
ON_EXCEPTION({ vector.pop_back(); });
...
Maaf telah menggali utas lama seperti itu, tetapi ada kesalahan besar pada alasan berikut:
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.
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
void DoStuff(vector<string> input)
{
list<Foo*> myList;
try
{
for (int i = 0; i < input.size(); ++i)
{
Foo* tmp = new Foo(input[i]);
if (!tmp)
throw;
myList.push_back(tmp);
}
DoSomeStuff(myList);
}
finally
{
while (!myList.empty())
{
delete myList.back();
myList.pop_back();
}
}
}
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:
void DoStuff(vector<string> input)
{
list<Foo*> myList;
try
{
for (int i = 0; i < input.size(); ++i)
{
Foo* tmp = new Foo(input[i]);
if (!tmp)
throw;
myList.push_back(tmp);
}
DoSomeStuff(myList);
}
catch(...)
{
}
while (!myList.empty())
{
delete myList.back();
myList.pop_back();
}
}
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.
new
tidak mengembalikan NULL, sebagai gantinya melempar pengecualian
std::shared_ptr
dan std::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;
int CMyApp::Run()
{
__try
{
int i = CWinApp::Run();
m_Exitok = MAGIC_EXIT_NO;
return i;
}
__finally
{
if (m_Exitok != MAGIC_EXIT_NO)
FaultHandler();
}
}
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.
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, penggunaan finally
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:
#include <gsl/gsl_util.h>
void example()
{
int handle = get_some_resource();
auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });
// Do a lot of stuff, return early and throw exceptions.
// clean_that_resource will always get called.
}
Implementasi dan penggunaan GSL sangat mirip dengan yang ada di jawaban Paolo.Bolzoni . Salah satu perbedaannya adalah bahwa objek yang dibuat gsl::finally()
tidak memiliki disable()
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.
Tidak juga, tetapi Anda dapat meniru mereka sampai batas tertentu, misalnya:
int * array = new int[10000000];
try {
// Some code that can throw exceptions
// ...
throw std::exception();
// ...
} catch (...) {
// The finally-block (if an exception is thrown)
delete[] array;
// re-throw the exception.
throw;
}
// The finally-block (if no exception was thrown)
delete[] array;
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.
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 ¹ yang finally
kata kunci di Jawa; itu menggunakan std::exception_ptr
dan teman-teman, fungsi lambda dan std::promise
, sehingga membutuhkan C++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.
#include <future>
template <typename Fun>
class FinallyHelper {
template <typename T> struct TypeWrapper {};
using Return = typename std::result_of<Fun()>::type;
public:
FinallyHelper(Fun body) {
try {
execute(TypeWrapper<Return>(), body);
}
catch(...) {
m_promise.set_exception(std::current_exception());
}
}
Return get() {
return m_promise.get_future().get();
}
private:
template <typename T>
void execute(T, Fun body) {
m_promise.set_value(body());
}
void execute(TypeWrapper<void>, Fun body) {
body();
}
std::promise<Return> m_promise;
};
template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
return FinallyHelper<Fun>(body);
}
Lalu ada makro yang sebenarnya.
#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try
#define finally }); \
true; \
({return __finally_helper.get();})) \
/***/
Dapat digunakan seperti ini:
void test() {
try_with_finally {
raise_exception();
}
catch(const my_exception1&) {
/*...*/
}
catch(const my_exception2&) {
/*...*/
}
finally {
clean_it_all_up();
}
}
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 saja std::promise
.
¹ CAVEAT: ada beberapa hal yang tidak berfungsi seperti versi java finally
. Dari atas kepala saya:
break
pernyataan dari dalam try
dancatch()
's blok, karena mereka hidup dalam fungsi lambda;catch()
blok setelahtry
: itu persyaratan C ++;try
dan catch()'s
blok, kompilasi akan gagal karena finally
makro akan diperluas ke kode yang ingin mengembalikan a void
. Ini bisa, err, dibatalkan dengan memiliki semacam finally_noreturn
makro.Semua dalam semua, saya tidak tahu apakah saya akan pernah menggunakan barang ini sendiri, tapi itu menyenangkan bermain dengannya. :)
catch(xxx) {}
blok yang mustahil di awal finally
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 sentinel nullptr
dikirim pada akhir proses untuk mematikan semua utas.
Jika C ++ mendukungnya, Anda ingin kode Anda terlihat seperti ini:
extern Queue downstream, upstream;
int Example()
{
try
{
while(!ExitRequested())
{
X* x = upstream.pop();
if (!x) break;
x->doSomething();
downstream.push(x);
}
}
finally {
downstream.push(nullptr);
}
}
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 ke push(nullptr)
dalam destruktor downstream
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:
class Finally
{
public:
Finally(std::function<void(void)> callback) : callback_(callback)
{
}
~Finally()
{
callback_();
}
std::function<void(void)> callback_;
};
dan inilah cara Anda menggunakannya:
extern Queue downstream, upstream;
int Example()
{
Finally atEnd([](){
downstream.push(nullptr);
});
while(!ExitRequested())
{
X* x = upstream.pop();
if (!x) break;
x->doSomething();
downstream.push(x);
}
}
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.
#include <vector>
#include <memory>
#include <list>
using namespace std;
class Foo
{
...
};
void DoStuff(vector<string> input)
{
list<unique_ptr<Foo> > myList;
for (int i = 0; i < input.size(); ++i)
{
myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
}
DoSomeStuff(myList);
}
Beberapa pengantar untuk menggunakan unique_ptr dengan wadah C ++ Standard Library ada di sini
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)
try{
// something that might throw exception
} catch( ... ){
// what to do with uknown exception
}
//final code to be called always,
//don't forget that it might throw some exception too
doSomeCleanUp();
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:
bool generalAppState = false;
try{
// something that might throw exception
//the very end of try block:
generalAppState = true;
} catch( ... ){
// what to do with uknown exception
}
//final code to be called only when exception was thrown,
//don't forget that it might throw some exception too
if( !generalAppState ){
doSomeCleanUpOfDirtyEnd();
}
//final code to be called only when no exception is thrown
//don't forget that it might throw some exception too
else{
cleanEnd();
}
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.)
try
{
...
goto finally;
}
catch(...)
{
...
goto finally;
}
finally:
{
...
}
finally
tidak dilakukan.