Kenapa Exception Ada
Di halaman sebelumnya kamu memakai enum class untuk memberi nama yang bermakna pada keadaan error. Itu bagus untuk hasil yang memang diperkirakan sebuah fungsi dan seharusnya diperiksa oleh pemanggil. Namun beberapa kegagalan berbeda: sebuah fungsi yang berada jauh di dalam call stack menemukan bahwa sebuah file tidak mau terbuka, atau sebuah argumen tidak masuk akal, dan fungsi itu tidak tahu apa yang harus dilakukan program. Mengembalikan kode error hanya berhasil kalau setiap pemanggil di sepanjang rantai ingat untuk mengeceknya dan meneruskannya ke atas. Lewatkan satu pengecekan, dan program terus berjalan dengan data sampah.
Exception menyelesaikan masalah ini. Saat terjadi kesalahan, kamu melempar (throw) sebuah objek. Eksekusi langsung berhenti, stack di-unwind (setiap objek lokal di antara titik throw dan penanganannya menjalankan destructor-nya), dan kendali melompat ke catch terdekat yang cocok. Exception yang tidak ditangani tidak bisa diabaikan diam-diam: kalau tidak ada yang menangkapnya, program memanggil std::terminate dan berhenti.
Halaman ini berfokus pada sisi pelemparan, yaitu objek error-nya sendiri. Halaman berikutnya membahas mekanisme try/catch secara mendetail.
Melempar Exception dan Pesan what()
Secara teknis kamu bisa melempar (throw) nilai apa saja; throw 42; atau throw "oops"; itu sah, tetapi jangan lakukan. Konvensi yang diikuti semua orang adalah melempar objek yang diturunkan dari std::exception. Base class itu mendeklarasikan satu method virtual, what(), yang mengembalikan const char* berisi penjelasan masalahnya. Mengikuti konvensi ini berarti satu catch (const std::exception& e) bisa menangani apa saja.
Header <stdexcept> menyediakan tipe siap pakai yang constructor-nya menerima pesan:
Perhatikan bahwa what() mengembalikan string yang persis sama dengan yang kamu pakai untuk membuat exception itu. Perhatikan juga bahwa kita menangkapnya dengan const exception& meskipun yang dilempar adalah runtime_error. Ini berhasil karena runtime_error adalah sebuah std::exception (hubungan yang akan kamu kenali dari halaman pewarisan).
Hierarki Exception Standar
Sebelum menulis tipe exception sendiri, cek dulu apakah standard library sudah punya yang cocok. Semuanya diturunkan dari std::exception dan terbagi menjadi dua keluarga di <stdexcept>:
logic_error: bug di logika program yang, secara prinsip, bisa ditangkap sebelum program dijalankan. Subtipenya antara laininvalid_argument,out_of_range,domain_error, danlength_error.runtime_error: kegagalan yang hanya muncul saat program berjalan dan bukan kesalahan pemrograman itu sendiri. Subtipenya antara lainrange_error,overflow_error, danunderflow_error.
Banyak fungsi library melempar exception ini untukmu. Misalnya, at() milik container vector mengecek batas dan melempar out_of_range alih-alih membiarkanmu membaca melewati ujungnya:
at() itu adalah pasangan aman dari v[9]. operator[] biasa tidak mengecek batas: membaca v[9] di sini adalah undefined behavior, bukan exception. Memilih at() adalah cara mengubah kerusakan diam-diam menjadi error yang bisa ditangkap.
Pilih tipe yang menggambarkan error-nya: invalid_argument saat pemanggil memberikan sesuatu yang tidak masuk akal, out_of_range untuk masalah indeks/key, runtime_error untuk "dunia luar gagal saat aku memakainya."
Menulis Tipe Exception Sendiri
Kalau tidak ada tipe standar yang cocok, misalnya kamu ingin melampirkan data tambahan, atau ingin menangkap (catch) error-mu secara khusus dan tidak yang lain, definisikan class yang diturunkan dari std::exception (atau salah satu subtipenya) dan override what(). Menurunkan dari std::runtime_error adalah jalan termudah, karena class itu sudah menyimpan pesannya dan mengimplementasikan what() untukmu:
Karena NetworkError membawa kode status, penanganannya bisa bereaksi terhadapnya: coba lagi untuk 5xx, menyerah untuk 4xx. String error biasa tidak bisa melakukan itu. Tipe buatan sendiri juga membuat catch (const NetworkError&) hanya menangkap masalah jaringan dan menyerahkan semua yang lain ke penanganan yang lebih umum di bawahnya.
Kalau kamu menurunkan langsung dari std::exception (bukan runtime_error), ingat untuk meng-override what() sendiri, dan tandai dengan noexcept agar cocok dengan signature base class-nya:
class ParseError : public std::exception {
public:
const char* what() const noexcept override {
return "failed to parse input";
}
};
Throw by Value, Catch by Reference
Inilah aturan paling penting dari exception C++, dan yang paling sering salah dilakukan pemula. Lempar objek by value dan tangkap by referensi const.
throw runtime_error("oops"); // by value - correct
catch (const runtime_error& e) { ... } // by const reference - correct
Menangkap by value, misalnya catch (std::exception e), menyalin exception ke objek base class dan memotong (slicing) bagian turunannya. Setelah terpotong, e.what() memanggil implementasi base, bukan versi yang kamu override, sehingga pesan yang kamu susun dengan cermat menghilang:
try {
throw NetworkError(503, "service unavailable");
} catch (std::exception e) { // by value - object slicing!
std::cout << e.what(); // generic message, status() is gone
}
Referensi (&) mempertahankan tipe dinamis yang sebenarnya, sehingga what() yang virtual dipanggil dengan benar dan kamu tetap bisa mengakses member turunannya. Tambahkan const karena kamu hanya membaca exception itu, tidak mengubahnya. Jangan pernah melempar pointer (throw new runtime_error(...)): penangkapnya harus melakukan delete, tetapi di jalur kode yang mana? Itulah persis kebocoran yang seharusnya dicegah oleh exception.
Berikutnya: try-catch
Sekarang kamu bisa membuat dan melempar (throw) exception yang tersusun dengan baik serta memilih tipe standar yang tepat untuk setiap kegagalan. Separuh cerita lainnya adalah sisi penangkapan. Halaman berikutnya membahas try/catch secara lengkap: mengurutkan beberapa blok catch dari yang paling spesifik ke yang paling umum, catch-all catch (...), melempar ulang dengan throw; saja, dan bagaimana RAII (ingat kembali smart pointer) menjamin resource-mu dilepaskan saat stack di-unwind.
Pertanyaan yang sering diajukan
Apa itu exception di C++?
Exception adalah objek yang menandakan error yang tidak bisa ditangani sendiri oleh fungsi saat ini. Kamu melemparnya dengan throw, stack di-unwind (objek lokal di sepanjang jalan dihancurkan), dan blok catch yang cocok di tingkat yang lebih atas mengambil alih. Exception memisahkan kode yang mendeteksi masalah dari kode yang memutuskan apa yang harus dilakukan.
Apa perbedaan throw dan return untuk error?
Nilai return harus dicek oleh pemanggil, dan itu gampang terlupa: program terus berjalan dengan data yang salah. Exception yang dilempar tidak bisa diabaikan: kalau tidak ada yang menangkapnya, program berhenti. Exception dipakai untuk kegagalan sungguhan (file tidak mau terbuka, input tidak valid); nilai kembalian tetap tepat untuk hasil biasa, termasuk kasus "tidak ditemukan" yang memang diperkirakan.
Apa fungsi method what() pada exception C++?
Setiap class yang diturunkan dari std::exception menyediakan method virtual what() yang mengembalikan const char* berisi penjelasan error-nya. Saat kamu menangkap exception, memanggil e.what() memberimu pesan yang bisa dibaca manusia, yang bisa kamu catat di log atau cetak. Tipe exception standar mengisinya dari string yang kamu berikan ke constructor-nya.