Ada beberapa cara, tetapi pertama-tama Anda harus memahami mengapa pembersihan objek itu penting, dan karenanya alasannya std::exitterpinggirkan di antara programmer C ++.
RAII dan Stack Unwinding
C ++ memanfaatkan idiom yang disebut RAII , yang secara sederhana berarti objek harus melakukan inisialisasi pada konstruktor dan pembersihan pada destruktor. Misalnya std::ofstreamkelas [boleh] membuka file selama konstruktor, kemudian pengguna melakukan operasi output di atasnya, dan akhirnya pada akhir siklus hidupnya, biasanya ditentukan oleh cakupannya, destruktor disebut yang pada dasarnya menutup file dan memerah setiap konten tertulis ke dalam disk.
Apa yang terjadi jika Anda tidak sampai ke destructor untuk menyiram dan menutup file? Siapa tahu! Tapi mungkin itu tidak akan menulis semua data yang seharusnya ditulis ke dalam file.
Sebagai contoh, pertimbangkan kode ini
#include <fstream>
#include <exception>
#include <memory>
void inner_mad()
{
throw std::exception();
}
void mad()
{
auto ptr = std::make_unique<int>();
inner_mad();
}
int main()
{
std::ofstream os("file.txt");
os << "Content!!!";
int possibility = /* either 1, 2, 3 or 4 */;
if(possibility == 1)
return 0;
else if(possibility == 2)
throw std::exception();
else if(possibility == 3)
mad();
else if(possibility == 4)
exit(0);
}
Apa yang terjadi di setiap kemungkinan adalah:
- Kemungkinan 1: Kembali pada dasarnya meninggalkan ruang lingkup fungsi saat ini, sehingga ia tahu tentang akhir siklus hidup
ossehingga memanggil destruktornya dan melakukan pembersihan yang tepat dengan menutup dan menyiram file ke disk.
- Kemungkinan 2: Melempar pengecualian juga menangani siklus hidup objek dalam lingkup saat ini, sehingga melakukan pembersihan yang tepat ...
- Kemungkinan 3: Di sini tumpukan unwinding mulai beraksi! Meskipun pengecualian dilemparkan pada
inner_mad, pelepas akan pergi melalui tumpukan maddan mainuntuk melakukan pembersihan yang tepat, semua objek akan dihancurkan dengan benar, termasuk ptrdan os.
- Kemungkinan 4: Nah, ini?
exitadalah fungsi C dan tidak disadari atau kompatibel dengan idiom C ++. Itu tidak melakukan pembersihan pada objek Anda, termasuk osdalam cakupan yang sama. Jadi file Anda tidak akan ditutup dengan benar dan karena alasan ini kontennya mungkin tidak akan pernah ditulis ke dalamnya!
- Kemungkinan lain: Ini hanya akan meninggalkan ruang lingkup utama, dengan melakukan implisit
return 0dan dengan demikian memiliki efek yang sama dengan kemungkinan 1, yaitu pembersihan yang tepat.
Tapi jangan terlalu yakin tentang apa yang saya katakan (terutama kemungkinan 2 dan 3); lanjutkan membaca dan kami akan mencari tahu cara melakukan pembersihan berdasarkan pengecualian yang tepat.
Kemungkinan Cara Untuk Mengakhiri
Kembali dari main!
Anda harus melakukan ini bila memungkinkan; selalu lebih suka untuk kembali dari program Anda dengan mengembalikan status keluar yang tepat dari utama.
Penelepon program Anda, dan mungkin sistem operasi, mungkin ingin tahu apakah program Anda seharusnya dilakukan dengan sukses atau tidak. Untuk alasan yang sama ini, Anda harus mengembalikan nol atau EXIT_SUCCESSuntuk memberi sinyal bahwa program berhasil dihentikan dan EXIT_FAILUREuntuk memberi sinyal program tidak berhasil, bentuk lain dari nilai pengembalian ditentukan oleh implementasi ( §18.5 / 8 ).
Namun Anda mungkin sangat dalam di tumpukan panggilan, dan mengembalikan semuanya mungkin menyakitkan ...
[Jangan] melempar pengecualian
Melempar pengecualian akan melakukan pembersihan objek yang tepat menggunakan stack unwinding, dengan memanggil destruktor dari setiap objek dalam cakupan sebelumnya.
Tapi inilah tujuannya ! Ini ditentukan oleh implementasi apakah stack unwinding dilakukan ketika pengecualian yang dilempar tidak ditangani (dengan klausa catch (...)) atau bahkan jika Anda memiliki noexceptfungsi di tengah tumpukan panggilan. Hal ini dinyatakan dalam §15.5.1 [except.terminate] :
Dalam beberapa situasi penanganan pengecualian harus ditinggalkan untuk teknik penanganan kesalahan yang kurang halus. [Catatan: Situasi ini adalah:
[...]
- ketika mekanisme penanganan pengecualian tidak dapat menemukan pawang untuk pengecualian yang dilemparkan (15.3), atau ketika pencarian untuk pawang (15.3) menemukan blok terluar dari fungsi dengan noexceptspesifikasi- yang tidak memungkinkan pengecualian (15.4), atau [...]
[...]
Dalam kasus seperti itu, std :: terminate () disebut (18.8.3). Dalam situasi di mana tidak ada handler yang cocok ditemukan, itu adalah implementasi yang ditentukan apakah stack dibatalkan atau tidak sebelum std :: terminate () disebut [...]
Jadi kita harus menangkapnya!
Jangan melempar pengecualian dan menangkapnya di main!
Karena pengecualian tanpa tertangkap mungkin tidak melakukan stack unwinding (dan akibatnya tidak akan melakukan pembersihan yang benar) , kita harus menangkap pengecualian di main dan kemudian mengembalikan status keluar ( EXIT_SUCCESSatau EXIT_FAILURE).
Jadi pengaturan yang mungkin bagus adalah:
int main()
{
/* ... */
try
{
// Insert code that will return by throwing a exception.
}
catch(const std::exception&) // Consider using a custom exception type for intentional
{ // throws. A good idea might be a `return_exception`.
return EXIT_FAILURE;
}
/* ... */
}
[Jangan] std :: keluar
Ini tidak melakukan segala macam tumpukan bersantai, dan tidak ada objek hidup di tumpukan akan memanggil destructor masing-masing untuk melakukan pembersihan.
Ini diberlakukan di §3.6.1 / 4 [basic.start.init] :
Mengakhiri program tanpa meninggalkan blok saat ini (misalnya, dengan memanggil fungsi std :: exit (int) (18.5)) tidak menghancurkan objek apa pun dengan durasi penyimpanan otomatis (12.4) . Jika std :: exit dipanggil untuk mengakhiri program selama penghancuran objek dengan durasi penyimpanan statis atau thread, program memiliki perilaku yang tidak ditentukan.
Pikirkan sekarang, mengapa Anda melakukan hal seperti itu? Berapa banyak benda yang telah Anda rusak sangat menyakitkan?
Alternatif [seburuk] lainnya
Ada cara lain untuk menghentikan program (selain mogok) , tetapi mereka tidak disarankan. Hanya demi klarifikasi, mereka akan disajikan di sini. Perhatikan bagaimana terminasi program yang normal tidak berarti stack unwinding tetapi keadaan oke untuk sistem operasi.
std::_Exit menyebabkan penghentian program normal, dan hanya itu.
std::quick_exitmenyebabkan penghentian program normal dan memanggil std::at_quick_exitpenangan, tidak ada pembersihan lainnya dilakukan.
std::exitmenyebabkan penghentian program normal dan kemudian memanggil std::atexitpenangan. Jenis pembersihan lainnya dilakukan seperti memanggil destruktor objek statis.
std::abortmenyebabkan penghentian program yang tidak normal, tidak ada pembersihan yang dilakukan. Ini harus dipanggil jika program diakhiri dengan cara yang benar-benar tidak terduga. Ini tidak akan melakukan apa-apa selain memberi sinyal pada OS tentang penghentian abnormal. Beberapa sistem melakukan dump inti dalam kasus ini.
std::terminatepanggilan panggilan std::terminate_handlermana yang std::abortsecara default.
main()pengembalian digunakan, dalam fungsi menggunakan nilai pengembalian yang tepat atau membuang Pengecualian yang tepat. Jangan gunakanexit()!