Ada satu pembanding yang memisahkan pelepasan manual dari RAII secara paling tegas: exception. Ketika throw dijalankan, eksekusi fungsi saat itu dihentikan dan runtime menyapu stack — menjalankan destructor setiap objek otomatis di sepanjang jalan hingga catch yang menangkap ditemukan. Masalahnya: delete yang kita tulis manual bukanlah destructor, melainkan baris biasa — dan baris biasa yang berada SETELAH titik throw tidak pernah dijalankan. Kebocoran pun lahir di jalur yang jarang dilalui, biasanya saat error. Mekanisme exception sendiri baru dibahas penuh pada Section Error Handling; di chapter ini kita memakai throw dan try/catch secukupnya untuk membuktikan satu klaim: resource yang dibungkus RAII tetap terlepas, sedangkan pelepasan manual bocor.
Masalah: Exception Melewati Baris delete #
Pola manual dari Chapter Resource Management punya asumsi fatal di dunia exception: bahwa eksekusi selalu mengalir sampai baris release. Contoh berikut melanggarnya — dan inilah pola bermasalah ketiga yang melengkapi lupa-delete serta early-return dari Chapter Memory Leaks:
void prosesData(bool valid) {
int* buffer = new int[1000]; // alokasi manual
if (!valid) {
throw std::runtime_error("data tidak valid");
// stack unwinding: baris di bawah TIDAK dijalankan
}
// ... olah data ...
delete[] buffer; // LEAK bila throw di atas terjadi
}Saat throw dijalankan, sisa badan fungsi — termasuk delete[] buffer — dilompati tanpa ampun. Array 4000 byte itu tetap tercatat terpakai sampai program berakhir, dan karena jalur error sering dipicu kejadian nyata, kebocoran ini terjadi persis saat sistem sedang tertekan. Memeriksa setiap throw dan memasang delete di semua cabang tidak realistis; solusinya harus bekerja tanpa kita mengingat satu jalur pun.
RAII: Pelepasan yang Bekerja di Jalur Exception #
RAII dari Chapter RAII sudah menjanjikan ini: destructor berjalan pada stack unwinding. Mari kita buktikan lewat program yang mengalokasikan dua resource lalu melempar exception di tengah jalan — satu resource dibungkus kelas RAII, satu lagi berupa std::unique_ptr<int[]> yang memanggil delete[] di destructornya:
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
class LogRAII {
public:
explicit LogRAII(const std::string& nama) : nama_(nama) {
std::cout << " [RAII] " << nama_ << " dialokasikan\n";
}
~LogRAII() {
std::cout << " [RAII] " << nama_ << " dilepas otomatis\n";
}
private:
std::string nama_;
};
void prosesData(bool dataValid) {
LogRAII berkas("handle-berkas");
std::unique_ptr<int[]> buffer = std::make_unique<int[]>(1000);
std::cout << " buffer 4000 byte siap dipakai\n";
if (!dataValid) {
throw std::runtime_error("data tidak valid");
// Semua resource di atas tetap dilepas: stack unwinding berjalan.
}
std::cout << " data diproses\n";
}
int main() {
try {
prosesData(false);
} catch (const std::runtime_error& e) {
std::cout << "Exception tertangkap: " << e.what() << "\n";
}
std::cout << "Program berlanjut normal\n";
return 0;
}Output yang Dihasilkan:
[RAII] handle-berkas dialokasikan
buffer 4000 byte siap dipakai
[RAII] handle-berkas dilepas otomatis
Exception tertangkap: data tidak valid
Program berlanjut normalBacaan pembuktiannya urut dari atas ke bawah:
- Baris 19-21 (dua resource di-acquire): kelas RAII dan
unique_ptr<int[]>masing-masing memegang satu resource; keduanya adalah objek otomatis di stack. - Baris 23-24 (
throw): exception dilempar sebelum fungsi sempat menyelesaikan tugasnya;data diprosestidak pernah tercetak. - Baris 3 (output
[RAII] handle-berkas dilepas otomatis): runtime menjalankan destructorberkassaat stack unwinding — resource pertama kembali meskithrowmenyela. unique_ptr<int[]>tanpa jejak cetak: pelepasannya tetap terjadi di destructornya; array 4000 byte tidak bocor, hanya tidak menulis log.- Baris 31-35 (
try/catch): exception ditangkap, pesannya dibaca lewate.what(), dan program berlanjut normal dengan nol resource tersangkut.
Kontraskan dengan blok bermasalah di atas: perbedaannya bukan pada jumlah baris, melainkan pada LOKASI logika pelepasan. Pada RAII, pelepasan tinggal di destructor — tempat yang pasti dikunjungi runtime pada semua jalur keluar, termasuk jalur exception.
Aturan yang Dibawa Pulang #
- Jangan pernah bergantung pada
deletemanual untuk melindungi resource dari exception — baris itu bisa dilompati kapan saja. - Bungkus setiap resource yang di-acquire dalam objek RAII segera setelah di-acquire:
unique_ptruntuk memori, kelas pustaka untuk berkas dan mutex, kelas buatan sendiri untuk resource khusus. - Jaga window antara acquire dan pembungkusan tetap nol: cukup satu
throwdi antaranewdanmake_uniqueuntuk menciptakan kebocoran — itulah alasanmake_uniquedianjurkan di atasneweksplisit.
Dengan exception tuntas, peta manajemen memori kita tinggal satu kotak yang belum dirangkum: bagaimana menggabungkan semua aturan section ini menjadi kebiasaan menulis kode yang idiomatik. Chapter berikutnya — penutup Section — merangkumnya menjadi aturan modern yang bisa kita pegang setiap hari.
Referensi: cppreference — exception safety , cppreference — RAII .