Apa Itu Destructor
Di halaman sebelumnya kamu melihat constructor, yaitu fungsi khusus yang berjalan saat objek lahir untuk menyiapkan keadaan awalnya. Destructor adalah kebalikannya: fungsi khusus yang berjalan saat objek mati, untuk membereskan sisa-sisanya.
Kamu mendeklarasikannya dengan nama class yang diawali tanda tilde (~). Ia tidak menerima parameter, tidak mengembalikan apa pun, dan sebuah class punya tepat satu destructor. Kamu hampir tidak pernah memanggilnya secara manual; C++ memanggilkannya untukmu di saat yang tepat.
Perhatikan bahwa pesan destructor dicetak setelah main menyelesaikan isinya, tetapi sebelum program keluar. Saat log keluar dari scope di kurung kurawal penutup, C++ menjalankan ~Logger() untukmu.
Kapan Destructor Berjalan
Waktu tepatnya bergantung pada tempat objek itu berada:
- Objek di stack (lokal) dihancurkan saat keluar dari scope, yaitu di
}penutup blok. - Objek di heap (dibuat dengan
new) dihancurkan saat kamu memanggildelete. Kalau kamu lupadelete, destructor-nya tidak pernah berjalan dan terjadi kebocoran memori.
Contoh ini memperlihatkan perbedaannya:
Objek dihancurkan dengan urutan kebalikan dari pembuatannya. a dibuat pertama, jadi ia mati paling akhir. Urutan LIFO (last-in, first-out) ini penting saat objek-objek saling bergantung.
Kenapa Destructor Penting: RAII
Kekuatan sebenarnya dari destructor adalah membuat pembersihan berjalan otomatis dan aman terhadap exception. Daripada mengingat untuk melepaskan resource di setiap jalur kode, kamu menaruh pelepasan itu di destructor dan membiarkan bahasa menjamin ia berjalan. Pola ini disebut RAII (Resource Acquisition Is Initialization), dan merupakan tulang punggung C++ modern.
Di sini sebuah class memiliki buffer di heap: ia mengalokasikannya di constructor dan membebaskannya di destructor, sehingga pemanggilnya tidak pernah menyentuh new/delete sendiri.
Wawasan pentingnya: bahkan kalau sebuah exception dilempar setelah squares dibuat, stack akan di-unwind dan ~IntArray() tetap berjalan. Jaminan itulah yang membuat RAII begitu andal, dan alasan kamu jarang menulis delete secara langsung di kode C++ yang baik.
Rule of Three (dan Five)
Class dengan destructor buatan sendiri hampir selalu memiliki resource mentah, dan itu memunculkan bahaya tersembunyi. Copy constructor dan copy assignment yang dibuat compiler melakukan salinan dangkal (shallow copy): yang disalin adalah pointer-nya, bukan buffer yang ditunjuknya. Sekarang dua objek memegang pointer yang sama, dan kedua destructor akan melakukan delete padanya, sehingga program crash karena double free.
IntArray a(5);
IntArray b = a; // shallow copy: a.data and b.data are the SAME pointer
// at scope end: b's destructor frees the buffer,
// then a's destructor frees it AGAIN -> undefined behavior (double free)
Inilah asal Rule of Three: kalau kamu menulis salah satu dari destructor, copy constructor, atau copy assignment operator, hampir pasti kamu butuh ketiganya. Di C++11 ke atas aturan ini diperluas menjadi Rule of Five, dengan tambahan move constructor dan move assignment.
Namun ada aturan yang lebih baik lagi, yaitu Rule of Zero: rancang class supaya kamu sama sekali tidak mengelola resource mentah. Simpan std::vector, std::string, atau smart pointer sebagai gantinya, dan destructor buatan compiler akan bekerja dengan benar tanpa usaha tambahan.
Jadikan Rule of Zero sebagai pilihan default. Tulis destructor sendiri hanya kalau kamu benar-benar memiliki resource mentah yang tidak dibungkus oleh tipe standar mana pun.
Virtual Destructor
Saat kamu menghapus objek lewat pointer base class, destructor-nya harus virtual. Kalau tidak, hanya bagian base-nya yang dihancurkan dan bagian turunannya bocor. Ini salah satu bug paling umum di kode polimorfik, dan secara default compiler tidak akan memperingatkanmu.
Tanpa virtual pada ~Base, delete p hanya akan memanggil ~Base(). Itu undefined behavior, dan bagian Derived dari objek tidak pernah dibersihkan. Patokannya: setiap class yang punya virtual function (base class polimorfik) butuh virtual destructor. Kamu akan melihat persis kenapa ini penting begitu kamu mulai menurunkan class.
Kesalahan Umum dan Jebakan
Beberapa jebakan menjebak hampir semua orang:
new/delete yang tidak berpasangan. Kalau kamu mengalokasikan dengan new[], bebaskan dengan delete[]. Mencampur new[] dengan delete biasa (atau sebaliknya) adalah undefined behavior.
Lupa virtual pada destructor base class. Seperti di atas, menghapus objek turunan lewat pointer base tanpa virtual destructor membuat bagian turunannya bocor. Kalau kamu menulis class yang dimaksudkan untuk diturunkan, jadikan destructor-nya virtual.
Membiarkan exception keluar dari destructor. Destructor yang melempar exception saat stack unwinding akan menghentikan programmu. Destructor secara implisit bersifat noexcept di C++ modern, jadi jaga agar kode pembersihan tidak melempar exception, atau tangkap exception itu di dalam destructor.
Menulis destructor yang tidak kamu butuhkan. Kalau member-mu sudah membersihkan dirinya sendiri, ~ClassName() {} yang kosong hanya menambah keramaian dan bisa diam-diam menonaktifkan operasi move. Kalau tidak ada yang perlu dibersihkan, jangan tulis destructor sama sekali.
Berikutnya: Pewarisan
Sekarang kamu sudah melihat siklus hidup lengkap sebuah objek: constructor menghidupkannya, destructor membersihkannya, dan destructor virtual menjaga pembersihan itu tetap benar saat satu class dibangun di atas class lain. Poin terakhir itu adalah gambaran awal dari ide besar berikutnya, yaitu pewarisan (inheritance), ketika sebuah class memakai ulang dan memperluas data serta perilaku class lain. Halaman berikutnya menunjukkan cara menurunkan satu class dari class lain, bagaimana pembuatan dan penghancuran objek berantai di sepanjang hierarki, dan bagaimana potongan-potongan yang baru kamu pelajari saling melengkapi.
Pertanyaan yang sering diajukan
Apa itu destructor di C++?
Destructor adalah member function khusus bernama ~ClassName() yang berjalan otomatis saat sebuah objek dihancurkan, yaitu saat objek keluar dari scope atau saat kamu melakukan delete padanya. Tugasnya adalah membersihkan: melepaskan memori, menutup file, atau membebaskan resource apa pun yang dimiliki objek. Ia tidak menerima parameter, tidak punya tipe kembalian, dan sebuah class hanya boleh punya satu.
Kapan destructor berjalan di C++?
Untuk objek lokal (di stack), destructor berjalan saat objek keluar dari scope, di kurung kurawal penutup }. Untuk objek heap yang dibuat dengan new, destructor berjalan saat kamu memanggil delete. Member dan base class dihancurkan otomatis sesudahnya, dengan urutan kebalikan dari pembuatannya.
Apakah aku selalu perlu menulis destructor di C++?
Tidak. Kalau class-mu hanya berisi member yang membersihkan dirinya sendiri (seperti std::string, std::vector, atau smart pointer), destructor yang dibuat compiler sudah cukup, jadi jangan menulisnya. Kamu hanya butuh destructor sendiri saat class-mu memiliki resource mentah, misalnya memori dari new atau file handle yang sedang terbuka.