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:

cpp Kode bermasalah — exception melewati delete
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:

cpp
#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:

text
  [RAII] handle-berkas dialokasikan
  buffer 4000 byte siap dipakai
  [RAII] handle-berkas dilepas otomatis
Exception tertangkap: data tidak valid
Program berlanjut normal

Bacaan pembuktiannya urut dari atas ke bawah:

  1. Baris 19-21 (dua resource di-acquire): kelas RAII dan unique_ptr<int[]> masing-masing memegang satu resource; keduanya adalah objek otomatis di stack.
  2. Baris 23-24 (throw): exception dilempar sebelum fungsi sempat menyelesaikan tugasnya; data diproses tidak pernah tercetak.
  3. Baris 3 (output [RAII] handle-berkas dilepas otomatis): runtime menjalankan destructor berkas saat stack unwinding — resource pertama kembali meski throw menyela.
  4. unique_ptr<int[]> tanpa jejak cetak: pelepasannya tetap terjadi di destructornya; array 4000 byte tidak bocor, hanya tidak menulis log.
  5. Baris 31-35 (try/catch): exception ditangkap, pesannya dibaca lewat e.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 delete manual 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_ptr untuk memori, kelas pustaka untuk berkas dan mutex, kelas buatan sendiri untuk resource khusus.
  • Jaga window antara acquire dan pembungkusan tetap nol: cukup satu throw di antara new dan make_unique untuk menciptakan kebocoran — itulah alasan make_unique dianjurkan di atas new eksplisit.

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 .