Chapter sebelumnya menjelaskan perjalanan exception secara konsep; sekarang saatnya kita menulis perhentiannya sendiri. Blok try menandai wilayah kode yang kita pantau, dan setiap blok catch di belakangnya adalah handler — pintu khusus yang hanya dibuka bila tipe exceptionnya cocok. Kita akan mengatur beberapa handler sekaligus, menangkap lewat const reference, memakai catch-all catch (...), dan yang terpenting: memahami bahwa urutan handler menentukan siapa yang menang, karena compiler menguji handler dari atas ke bawah dan berhenti pada pasangan pertama.
Anatomi try dan catch #
Bentuk paling sederhananya seperti ini: pernyataan yang berpotensi gagal ditaruh di dalam blok try, dan handler ditulis sebagai catch (const T& e) tepat setelahnya. Ketika throw terjadi di dalam wilayah try, eksekusi badan blok langsung dihentikan — pernyataan setelah titik lempar tidak pernah berjalan — lalu compiler mencocokkan tipe objek exception dengan parameter handler satu per satu, dari atas ke bawah. Handler pertama yang cocok mengambil alih; setelah badannya selesai, program melanjutkan dari pernyataan setelah rantai catch. Bila tidak ada handler yang cocok di blok ini, perjalanan propagasi berlanjut ke luar, seperti yang dibahas pada Chapter Exception.
Mengapa parameter handler ditulis const std::out_of_range& e, bukan std::out_of_range e? Dua alasan teknis: reference menghindari penyalinan objek exception (yang bisa berisi string panjang), dan menangkap lewat reference menjaga tipe asli exception tetap utuh. Bila exception dari class turunan ditangkap by value ke parameter class dasar, hanya bagian dasarnya yang tersalin — fenomena object slicing — dan informasi khas turunannya hilang. Menangkap by value juga berarti penyalinan tambahan; sedangkan menangkap by reference tidak menyalin sama sekali. Keyword const menambah jaminan bahwa handler tidak mengubah objek exception yang sedang ia tangani.
Beberapa Handler dan catch-all #
Satu blok try boleh diikuti banyak handler, satu untuk tiap tipe yang berbeda perlakuan. Program berikut memicu tiga jenis exception dari satu titik yang sama — std::out_of_range dari vector::at(), std::invalid_argument dari std::stoi(), dan sebuah int polos yang dilempar untuk menunjukkan peran catch-all:
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
void jalankanSkenario(int skenario) {
std::vector<int> stok{3, 5, 8};
try {
// Wilayah yang dipantau: exception apa pun yang dilempar di sini
// diuji ke handler-hander di bawah, dari atas ke bawah.
if (skenario == 1) {
std::cout << " mengakses elemen ke-99 lewat at()\n";
int jumlah = stok.at(99); // lempar std::out_of_range
(void)jumlah;
} else if (skenario == 2) {
std::cout << " mengonversi teks kosong lewat stoi()\n";
int n = std::stoi(""); // lempar std::invalid_argument
(void)n;
} else {
std::cout << " melempar int, bukan tipe pustaka standar\n";
throw 42; // tipe bebas: int
}
} catch (const std::out_of_range& e) { // paling spesifik dulu
std::cout << " handler out_of_range: " << e.what() << "\n";
} catch (const std::invalid_argument& e) {
std::cout << " handler invalid_argument: " << e.what() << "\n";
} catch (...) { // catch-all: tipe apa pun
std::cout << " handler catch(...) menangkap tanpa melihat tipe\n";
}
}
int main() {
for (int skenario : {1, 2, 3}) {
std::cout << "skenario " << skenario << ":\n";
jalankanSkenario(skenario);
}
std::cout << "main() selesai dengan normal\n";
return 0;
}Output yang Dihasilkan:
skenario 1:
mengakses elemen ke-99 lewat at()
handler out_of_range: vector::_M_range_check: __n (which is 99) >= this->size() (which is 3)
skenario 2:
mengonversi teks kosong lewat stoi()
handler invalid_argument: stoi
skenario 3:
melempar int, bukan tipe pustaka standar
handler catch(...) menangkap tanpa melihat tipe
main() selesai dengan normalBagian-bagian yang menentukan:
- Baris 10-22 (badan
try): tiga jalur yang masing-masing melempar tipe berbeda. Perhatikan bahwa hanya satu jalur yang dieksekusi per pemanggilan. - Baris 23-24 (
catch (const std::out_of_range& e)): handler paling spesifik. Untuk skenario 1, ia yang menang dan mencetak pesan bawaan dariat(). - Baris 25-27 (
catch (const std::invalid_argument& e)): handler kedua;std::stoi("")melemparnya dengan pesan"stoi". - Baris 28-30 (
catch (...)): elipsis berarti “tipe apa pun”.throw 42tidak cocok dengan dua handler di atasnya, tetapi cocok dengan ini. Catch-all juga menampung exception sistem sepertistd::bad_allocbila kita tidak ingin menyebutnya satu per satu. - Baris 41 (loop di
main()): program benar-benar berlanjut setelah setiap penanganan — catch bukan jalan pintas keluar program, melainkan kembali ke alur normal.
Pesan vector::_M_range_check... pada output adalah teks bawaan libstdc++ dari GCC 14 saat at() menolak indeks di luar jangkauan; strukturnya deterministik untuk input yang sama, tetapi tidak ada jaminan teksnya sama di compiler lain — itulah mengapa membandingkan e.what() secara persis bukan ide yang baik, sedangkan menanganinya berdasar tipe exception sangatlah andal.
Urutan Handler: Paling Spesifik Dulu #
Karena compiler menguji handler dari atas ke bawah dan berhenti pada kecocokan pertama, handler exception turunan harus ditulis DI ATAS handler exception dasarnya. std::out_of_range adalah turunan dari std::logic_error, yang turunan dari std::exception — jadi catch (const std::exception&) akan menangkap semuanya, termasuk yang sebenarnya ingin kita perlakukan khusus. Kesalahan penulisan berikut bahkan diberi peringatan oleh compiler. Kode urutan-salah.cpp:
#include <stdexcept>
#include <vector>
int main() {
std::vector<int> v{1, 2, 3};
try {
int x = v.at(99);
(void)x;
} catch (const std::exception& e) {
(void)e;
} catch (const std::out_of_range& e) {
(void)e;
}
return 0;
}Mengompilasinya dengan g++ -std=c++23 -Wall -Wextra urutan-salah.cpp menghasilkan warning (eksekusi nyata):
urutan-salah.cpp:11:7: warning: exception of type ‘std::out_of_range’ will be caught by earlier handler [-Wexceptions]
11 | } catch (const std::out_of_range& e) {
| ^~~~~
urutan-salah.cpp:9:7: note: for type ‘std::exception’
9 | } catch (const std::exception& e) {
| ^~~~~Handler std::out_of_range pada kode itu tidak akan pernah menangkap apa pun — exception yang sama sudah dijemput handler std::exception di atasnya. GCC 14 cukup perhatian untuk memberi warning -Wexceptions, tetapi kode yang benar lebih baik daripada kode yang diingatkan: susun handler dari yang paling spesifik ke paling umum, dan letakkan catch (...) selalu paling bawah.
Sekarang kita tahu cara menangkap; berikutnya kita menengok sisi yang melempar. Chapter berikutnya membahas pernyataan throw secara penuh: apa yang boleh dilempar, mengapa dilempar by value dan ditangkap by reference, cara melempar ulang dengan throw;, serta apa yang terjadi ketika constructor ikut melempar.
Referensi: cppreference — try block , cppreference — exceptions .