Chapter sebelumnya menutup dengan fakta yang tidak nyaman: banyak kegagalan hanya bisa dideteksi saat program berjalan. Pertanyaan sesungguhnya menjadi bagaimana sebuah function melaporkan kegagalan itu kepada pihak yang mampu menanganinya. Exception adalah jawaban C++: sebuah objek yang dibuat pada titik kegagalan terdeteksi, lalu berpropagasi naik melewati function-function pemanggil sampai seseorang menangkapnya. Kekuatan mekanisme ini pada pemisahan dua peran yang sebelumnya terpaksa bercampur — kode yang mendeteksi masalah tidak lagi harus tahu cara memulihkannya, dan kode yang memulihkan tidak perlu tahu di mana masalahnya muncul. Pada chapter ini kita membangun intuisi alurnya terlebih dahulu; sintaks try/catch yang lengkap kita bedah pada chapter berikutnya.
Masalah yang Dijawab oleh Exception #
Tanpa exception, sebuah function yang gagal harus melaporkannya lewat nilai kembalian: -1 berarti error, atau parameter bool& ok, atau struct berisi status. Pola itu memaksa setiap level pemanggil menguraikan laporan itu lagi dan lagi, bahkan ketika level tersebut tidak punya izin atau pengetahuan untuk memulihkan situasi. Pada program nyata, rantai pemanggilan bisa sedalam sepuluh level, sementara yang mampu menampilkan dialog kesalahan biasanya hanya main(). Dengan exception, function paling dalam cukup melempar laporan kegagalan, dan laporan itu melompati semua level perantara menuju handler yang ditunjuk. Kode perantara tetap bersih: tidak ada pemeriksaan status di sana karena memang bukan tugasnya.
Alur: throw, Propagasi, catch #
Mekanisme exception berdiri di atas tiga momen. throw adalah pernyataan melempar: ekspresi di belakangnya dijadikan objek exception yang disimpan compiler. Propagasi adalah perjalanan objek itu dari function yang melempar naik melalui rantai pemanggil, mencari handler yang tipe parameternya cocok. catch adalah pemberhentian perjalanan: blok milik try yang pas menampung objek tersebut, dan program melanjutkan eksekusi setelah blok catch.
Diagram di atas menjelaskan tiga hal yang akan kita buktikan lewat program: objek exception dibuat di titik throw, perjalanannya menyapu destructor setiap function perantara, dan perhentiannya ada di handler pertama yang tipenya cocok.
Stack Unwinding di Buktikan dengan Program #
Bagian perjalanan exception sering terasa abstrak, jadi kita pasang objek Jejak di setiap function — objeknya mencetak pesan setiap kali constructor dan destructor-nya berjalan. Perhatikan urutan pencetakan pada output:
#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>
// Objek kecil yang mencatat masuk-keluar scope: kita bisa melihat
// urutan stack unwinding dengan mata telanjang.
struct Jejak {
std::string nama;
explicit Jejak(std::string n) : nama(std::move(n)) {
std::cout << " masuk " << nama << "\n";
}
~Jejak() {
std::cout << " keluar " << nama << " (destructor berjalan)\n";
}
};
// Level 3: yang mendeteksi kegagalan. Ia tidak tahu siapa yang
// akan menangani, jadi ia hanya MELAPORKAN lewat throw.
int bacaHarga(const std::string& teks) {
Jejak jejak{"bacaHarga"};
if (teks != "15000") {
throw std::invalid_argument("harga tidak berupa angka: " + teks);
}
return 15000;
}
// Level 2: yang memproses. Ia tidak menangani exception, sehingga
// exception lewat ke atas dan menghentikan sisanya.
void prosesPesanan(const std::string& teks) {
Jejak jejak{"prosesPesanan"};
int harga = bacaHarga(teks);
std::cout << " harga terbaca: " << harga << "\n";
}
int main() {
std::cout << "mulai\n";
try {
prosesPesanan("dua puluh");
} catch (const std::invalid_argument& e) {
std::cout << "ditangkap di main(): " << e.what() << "\n";
}
std::cout << "program berlanjut normal\n";
return 0;
}Output yang Dihasilkan:
mulai
masuk prosesPesanan
masuk bacaHarga
keluar bacaHarga (destructor berjalan)
keluar prosesPesanan (destructor berjalan)
ditangkap di main(): harga tidak berupa angka: dua puluh
program berlanjut normalMari kita bedah alurnya baris demi baris:
- Baris 16-23 (
struct Jejak): objek pencatat sederhana; pesan constructor dan destructor-nya menjadi saksi perjalanan eksekusi. - Baris 26-33 (
bacaHarga): level yang mendeteksi kegagalan. Saat input bukan"15000", pernyataanthrow std::invalid_argument(...)membuat objek exception berisi pesan, lalu eksekusi function berhenti di situ — barisreturn 15000;tidak pernah dicapai. - Baris 36-41 (
prosesPesanan): perhatikan output — function ini tidak pernah mencetakharga terbaca. Penugasanint harga = bacaHarga(teks);terputus oleh exception, dan sisanya dibatalkan. - Baris 14 dan 16 (destructor
Jejak): dua bariskeluar ...pada output adalah bukti stack unwinding — proses di mana compiler memanggil destructor setiap objek lokal yang sudah selesai dibangun, mulai dari function terdalam (bacaHarga) ke luar (prosesPesanan), sebelum handler ditemukan. - Baris 45-51 (
try/catchdimain()): tipe exceptionstd::invalid_argumentcocok dengan parameter handler, sehingga perjalanan berhenti;e.what()menyediakan pesan yang kita sematkan saatthrow. - Baris 52: setelah handler selesai, program berlanjut normal dari titik setelah blok catch — tidak ada yang mengulang atau melompat kembali.
Proses menyapu destructor itu bukan bonus teknis, melainkan fondasi keamanan sumber daya di C++. Karena setiap objek otomatis dibersihkan saat unwinding, resource yang dibungkus objek RAII — std::ofstream, std::vector, std::unique_ptr — kembali terlepas dengan benar bahkan ketika exception melewati functionnya. Kita sudah membahas idiom ini pada Chapter RAII, dan Chapter Exception-Safe Resource Management menguji jaminannya lebih jauh.
Exception Bukan untuk Alur Normal #
Kekuatan exception membuatnya menggoda dipakai di mana-mana — jangan. Exception dirancang untuk kegagalan yang luar biasa: kondisi yang membuat function tidak dapat memenuhi kontraknya, seperti berkas yang tidak ada, jaringan yang putus, atau data yang tidak valid. Menggunakan throw sebagai cara keluar dari loop atau sebagai nilai kembalian “alternatif” membuat alur program tidak bisa lagi dibaca dari kode, karena jalur-jalur tersembunyi menyilang di mana-mana. Ada juga biayanya: compiler mengoptimalkan jalur tanpa exception agar nyaris tanpa ongkos, tetapi melempar exception itu mahal — jauh lebih mahal daripada satu perbandingan if. Aturan praktisnya: pakai return value atau tipe hasil untuk kondisi yang diharapkan terjadi, dan pakai exception untuk kondisi yang, jika terjadi, memang harus dikomplain.
Dengan pemahaman bahwa exception = throw + propagasi + catch, kita siap menulis handler-nya sendiri. Chapter berikutnya membedah sintaks try dan catch secara penuh: menangkap dengan reference, menyusun beberapa handler, dan urutan yang menentukan siapa menang lebih dulu.
Referensi: cppreference — exceptions , cppreference — std::exception .