Constructor menjawab pertanyaan “bagaimana object dimulai”; destructor menjawab pasangannya: “bagaimana object berakhir”. Destructor adalah member function spesial yang dijalankan compiler secara otomatis tepat sebelum object berhenti ada — bukan oleh kita, dan tidak bisa ditunda. Pasangan constructor-destructor inilah yang membuat C++ mampu merapikan resource secara otomatis: file ditutup, koneksi dilepas, memori dikembalikan, semuanya pada saat yang bisa diprediksi. Chapter ini membuktikan kapan destructor berjalan dan urutannya.
Apa itu Destructor #
Destructor ditulis dengan tanda tilde di depan nama class: ~NamaClass(). Seperti constructor, ia tidak punya return type dan tidak menerima parameter, sehingga satu class hanya boleh punya satu destructor — tidak ada overloading di sini. Bila kita tidak menulisnya, compiler membangkitkan destructor kosong secara otomatis; destructor buatan compiler itu cukup untuk class yang member-nya semuanya mengurus diri sendiri seperti int dan std::string.
Destructor menjadi wajib ditulis sendiri ketika class mengelola resource di luar data member biasanya — memori yang diperoleh dari new, file yang dibuka, atau mutex yang dikunci. Ide dasarnya sederhana: apa pun yang diperoleh saat konstruksi dilepaskan saat destruksi, oleh class itu sendiri, tanpa mengandalkan disiplin pemanggil. Pola ini disebut RAII (Resource Acquisition Is Initialization) dan menjadi inti gaya C++ modern — jargon penting yang akan terus kita temui ke depan.
Kapan Destructor Dipanggil #
Compiler memanggil destructor pada titik masa hidup object berakhir, dan titik itu deterministik. Untuk object lokal (di stack), destruksi terjadi saat eksekusi keluar dari scope tempat object dibuat — akhir blok { }, akhir fungsi, atau return. Untuk object yang dibuat di heap lewat new — mekanisme memori dinamis yang dibahas tuntas pada Section Manajemen Memori — destructor baru berjalan ketika program mengeksekusi delete terhadap pointer itu. Tidak ada jalur ketiga yang bisa kita pilih: kita tidak pernah memanggil destructor secara manual.
Kepastian ini berbeda dari bahasa ber-garbage collector, di mana pembersihan bisa terjadi kapan saja (atau tidak terjadi bila program selesai dulu). Di C++, bila sebuah object “harus menutup koneksi sebelum berhenti”, kita bisa menaruh logika itu di destructor dan menyerahkan waktunya ke aturan scope. Kepastian inilah yang nantinya dimanfaatkan smart pointer dan seluruh pustaka resource di C++.
Urutan Destruksi: Kebalikan Konstruksi #
Aturan urutannya bisa dihafal satu kalimat: object yang dibuat terakhir dihancurkan lebih dulu, persis seperti tumpukan (stack). Program berikut memakai class Pelacak yang mencetak jejak di constructor dan destructor, sehingga urutannya terlihat tanpa perlu membayangkan:
#include <iostream>
#include <string>
class Pelacak {
private:
std::string nama;
public:
Pelacak(const std::string& n) : nama(n) {
std::cout << " konstruksi: " << nama << "\n";
}
~Pelacak() {
std::cout << " destruksi : " << nama << "\n";
}
};
int main() {
std::cout << "masuk main\n";
Pelacak luar("luar");
{
std::cout << "masuk blok dalam\n";
Pelacak dalam1("dalam-1");
Pelacak dalam2("dalam-2");
std::cout << "keluar blok dalam\n";
}
std::cout << "kembali di main\n";
return 0;
}Output yang Dihasilkan:
masuk main
konstruksi: luar
masuk blok dalam
konstruksi: dalam-1
konstruksi: dalam-2
keluar blok dalam
destruksi : dalam-2
destruksi : dalam-1
kembali di main
destruksi : luarBedah urutannya:
- Baris 23-24 (
Pelacak luar("luar");):luardibuat langsung di scopemain(), maka ia paling lama hidup — destruksinya menunggu sampai baris terakhir program. - Baris 26-30 (blok bersarang):
dalam1dandalam2dibuat di dalam blok{ }. Keduanya hanya hidup selama blok itu berjalan. - Baris 31-33 (keluar blok): begitu kurung kurawal penutup dilewati, destructor dipanggil untuk object di blok itu — dan urutannya terbalik:
dalam-2lebih dulu, barudalam-1. Bariskeluar blok dalamdicetak setelah keduanya, karena pernyataan itu sendiri berada di dalam blok. - Baris terakhir output (
destruksi : luar): destructorluarberjalan setelahreturn 0;dieksekusi, saatmain()selesai — membuktikan destruksi menempel pada akhir scope, bukan pada pernyataan terakhir yang kita tulis.
Sifat “kebalikan konstruksi” ini berlaku bertingkat pada setiap scope, bukan hanya urutan dua object. Dalam praktiknya, artinya object yang berdiri lebih lama (misalnya resource besar) pasti masih ada saat object pendampingnya dibongkar — asumsi yang sering dipakai class untuk berjaga-jaga.
Destructor dan Object di Heap #
Untuk object yang dibuat dengan new, destructor tidak menunggu akhir scope — ia berjalan tepat saat delete dieksekusi. Program berikut membandingkan keduanya dalam satu main():
#include <iostream>
#include <string>
class Koneksi {
private:
std::string target;
public:
Koneksi(const std::string& t) : target(t) {
std::cout << "koneksi ke " << target << " dibuka\n";
}
~Koneksi() {
std::cout << "koneksi ke " << target << " ditutup\n";
}
};
int main() {
Koneksi lokal("localhost");
Koneksi* jauh = new Koneksi("server.example.com"); // object dibuat di heap
delete jauh; // destructor dipanggil di sini
jauh = nullptr; // pointer tidak lagi memegang alamat
std::cout << "main selesai\n";
return 0;
}Output yang Dihasilkan:
koneksi ke localhost dibuka
koneksi ke server.example.com dibuka
koneksi ke server.example.com ditutup
main selesai
koneksi ke localhost ditutupPerhatikan urutan empat peristiwa terakhir: delete jauh; memicu destructor object heap di tengah-tengah fungsi — jauh sebelum main() berakhir — sehingga koneksi ke server.example.com ditutup muncul sebelum main selesai. Sebaliknya, lokal menunggu akhir scope, dan destroksinya justru menjadi pernyataan terakhir yang dijalankan. Ini bukan keanehan; itu dua aturan masa hidup yang berjalan berdampingan secara konsisten.
Constructor dan destructor kini sudah lengkap dalam genggaman kita. Ada satu tokoh yang diam-diam hadir di setiap member function sepanjang Section ini — pointer tersembunyi bernama this — dan Chapter berikutnya membongkarnya.
Rujukan resmi: Destructors (cppreference) , RAII (cppreference) .