Ada beberapa cara, tetapi pertama-tama Anda harus memahami mengapa pembersihan objek itu penting, dan karenanya alasannya std::exit
terpinggirkan 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::ofstream
kelas [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
os
sehingga 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 mad
dan main
untuk melakukan pembersihan yang tepat, semua objek akan dihancurkan dengan benar, termasuk ptr
dan os
.
- Kemungkinan 4: Nah, ini?
exit
adalah fungsi C dan tidak disadari atau kompatibel dengan idiom C ++. Itu tidak melakukan pembersihan pada objek Anda, termasuk os
dalam 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 0
dan 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_SUCCESS
untuk memberi sinyal bahwa program berhasil dihentikan dan EXIT_FAILURE
untuk 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 noexcept
fungsi 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 noexcept
spesifikasi- 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_SUCCESS
atau 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_exit
menyebabkan penghentian program normal dan memanggil std::at_quick_exit
penangan, tidak ada pembersihan lainnya dilakukan.
std::exit
menyebabkan penghentian program normal dan kemudian memanggil std::atexit
penangan. Jenis pembersihan lainnya dilakukan seperti memanggil destruktor objek statis.
std::abort
menyebabkan 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::terminate
panggilan panggilan std::terminate_handler
mana yang std::abort
secara default.
main()
pengembalian digunakan, dalam fungsi menggunakan nilai pengembalian yang tepat atau membuang Pengecualian yang tepat. Jangan gunakanexit()
!