Destruktor (C++)
Destruktor adalah fungsi anggota yang dipanggil secara otomatis ketika objek keluar dari cakupan atau secara eksplisit dihancurkan oleh panggilan ke delete
atau delete[]
. Destruktor memiliki nama yang sama dengan kelas dan didahului oleh tilde (~
). Misalnya, destruktor untuk kelas String
dinyatakan: ~String()
.
Jika Anda tidak menentukan destruktor, pengkompilasi menyediakan yang default, dan untuk beberapa kelas, ini cukup. Anda perlu menentukan destruktor kustom ketika kelas mempertahankan sumber daya yang harus dirilis secara eksplisit, seperti menangani sumber daya sistem atau pointer ke memori yang harus dirilis ketika instans kelas dihancurkan.
Pertimbangkan deklarasi String
kelas berikut:
// spec1_destructors.cpp
#include <string> // strlen()
class String
{
public:
String(const char* ch); // Declare the constructor
~String(); // Declare the destructor
private:
char* _text{nullptr};
};
// Define the constructor
String::String(const char* ch)
{
size_t sizeOfText = strlen(ch) + 1; // +1 to account for trailing NULL
// Dynamically allocate the correct amount of memory.
_text = new char[sizeOfText];
// If the allocation succeeds, copy the initialization string.
if (_text)
{
strcpy_s(_text, sizeOfText, ch);
}
}
// Define the destructor.
String::~String()
{
// Deallocate the memory that was previously reserved for the string.
delete[] _text;
}
int main()
{
String str("We love C++");
}
Dalam contoh sebelumnya, destruktor String::~String
menggunakan delete[]
operator untuk membatalkan alokasi ruang yang dialokasikan secara dinamis untuk penyimpanan teks.
Mendeklarasikan destruktor
Destruktor adalah fungsi dengan nama yang sama dengan kelas tetapi didahului oleh tilde (~
)
Beberapa aturan mengatur deklarasi destruktor. Destruktor:
- Jangan terima argumen.
- Jangan mengembalikan nilai (atau
void
). - Tidak dapat dinyatakan sebagai
const
, ,volatile
ataustatic
. Namun, mereka dapat dipanggil untuk penghancuran objek yang dinyatakan sebagaiconst
, ,volatile
ataustatic
. - Dapat dinyatakan sebagai
virtual
. Dengan menggunakan destruktor virtual, Anda dapat menghancurkan objek tanpa mengetahui jenisnya—destruktor yang benar untuk objek dipanggil menggunakan mekanisme fungsi virtual. Destruktor juga dapat dinyatakan sebagai fungsi virtual murni untuk kelas abstrak.
Menggunakan destruktor
Destruktor dipanggil ketika salah satu peristiwa berikut terjadi:
- Objek lokal (otomatis) dengan cakupan blok keluar dari cakupan.
- Gunakan
delete
untuk membatalkan alokasi objek yang dialokasikan menggunakannew
. Menggunakandelete[]
hasil dalam perilaku yang tidak terdefinisi. - Gunakan
delete[]
untuk membatalkan alokasi objek yang dialokasikan menggunakannew[]
. Menggunakandelete
hasil dalam perilaku yang tidak terdefinisi. - Masa pakai objek sementara berakhir.
- Program berakhir dan objek global atau statis ada.
- Destruktor secara eksplisit disebut menggunakan nama fungsi destruktor yang sepenuhnya memenuhi syarat.
Destruktor dapat dengan bebas memanggil fungsi anggota kelas dan mengakses data anggota kelas.
Ada dua batasan penggunaan destruktor:
Anda tidak dapat mengambil alamatnya.
Kelas turunan tidak mewarisi destruktor kelas dasar mereka.
Urutan penghancuran
Ketika objek keluar dari cakupan atau dihapus, urutan peristiwa dalam penghancuran lengkapnya adalah sebagai berikut:
Destruktor kelas dipanggil, dan isi fungsi destruktor dijalankan.
Destruktor untuk objek anggota nonstatis dipanggil dalam urutan terbalik di mana mereka muncul dalam deklarasi kelas. Daftar inisialisasi anggota opsional yang digunakan dalam konstruksi anggota ini tidak memengaruhi urutan konstruksi atau penghancuran.
Destruktor untuk kelas dasar non-virtual dipanggil dalam urutan deklarasi terbalik.
Destruktor untuk kelas dasar virtual dipanggil dalam urutan deklarasi terbalik.
// order_of_destruction.cpp
#include <cstdio>
struct A1 { virtual ~A1() { printf("A1 dtor\n"); } };
struct A2 : A1 { virtual ~A2() { printf("A2 dtor\n"); } };
struct A3 : A2 { virtual ~A3() { printf("A3 dtor\n"); } };
struct B1 { ~B1() { printf("B1 dtor\n"); } };
struct B2 : B1 { ~B2() { printf("B2 dtor\n"); } };
struct B3 : B2 { ~B3() { printf("B3 dtor\n"); } };
int main() {
A1 * a = new A3;
delete a;
printf("\n");
B1 * b = new B3;
delete b;
printf("\n");
B3 * b2 = new B3;
delete b2;
}
A3 dtor
A2 dtor
A1 dtor
B1 dtor
B3 dtor
B2 dtor
B1 dtor
Kelas dasar virtual
Destruktor untuk kelas dasar virtual dipanggil dalam urutan terbalik penampilan mereka dalam grafik acyclic yang diarahkan (traversal kedalaman, kiri-ke-kanan, postorder). gambar berikut menggambarkan grafik warisan.
Lima kelas, berlabel A hingga E, diatur dalam grafik warisan. Kelas E adalah kelas dasar B, C, dan D. Kelas C dan D adalah kelas dasar A dan B.
Berikut ini mencantumkan definisi kelas untuk kelas yang diperlihatkan dalam gambar:
class A {};
class B {};
class C : virtual public A, virtual public B {};
class D : virtual public A, virtual public B {};
class E : public C, public D, virtual public B {};
Untuk menentukan urutan penghancuran kelas dasar virtual dari objek jenis E
, pengkompilasi menyusun daftar dengan menerapkan algoritma berikut:
- Melintasi grafik ke kiri, dimulai dari titik terdalam dalam grafik (dalam hal ini,
E
). - Lakukan traversal ke kiri hingga semua simpul telah dikunjungi. Perhatikan nama simpul saat ini.
- Kunjungi kembali simpul sebelumnya (bawah dan ke kanan) untuk mengetahui apakah node yang diingat adalah kelas dasar virtual.
- Jika simpul yang diingat adalah kelas dasar virtual, pindai daftar untuk melihat apakah node tersebut telah dimasukkan. Jika bukan kelas dasar virtual, abaikan.
- Jika simpul yang diingat belum ada dalam daftar, tambahkan ke bagian bawah daftar.
- Melintasi grafik ke atas dan di sepanjang jalur berikutnya ke kanan.
- Buka langkah 2.
- Ketika jalur naik terakhir habis, perhatikan nama simpul saat ini.
- Buka langkah 3.
- Lanjutkan proses ini hingga simpul bawah lagi menjadi simpul saat ini.
Oleh karena itu, untuk kelas E
, urutan penghancuran adalah:
- Kelas
E
dasar non-virtual . - Kelas
D
dasar non-virtual . - Kelas
C
dasar non-virtual . - Kelas
B
dasar virtual . - Kelas
A
dasar virtual .
Proses ini menghasilkan daftar entri unik yang diurutkan. Tidak ada nama kelas yang muncul dua kali. Setelah daftar dibuat, daftar berjalan dalam urutan terbalik, dan destruktor untuk setiap kelas dalam daftar dari yang terakhir hingga yang pertama dipanggil.
Urutan konstruksi atau penghancuran terutama penting ketika konstruktor atau destruktor dalam satu kelas mengandalkan komponen lain yang dibuat terlebih dahulu atau bertahan lebih lama—misalnya, jika destruktor untuk A
(dalam angka yang ditunjukkan sebelumnya) bergantung pada B
masih ada ketika kodenya dijalankan, atau sebaliknya.
Interdependensi seperti itu antara kelas dalam grafik warisan secara inheren berbahaya karena kelas yang diturunkan kemudian dapat mengubah yang merupakan jalur paling kiri, sehingga mengubah urutan konstruksi dan penghancuran.
Kelas dasar non-virtual
Destruktor untuk kelas dasar non-virtual dipanggil dalam urutan terbalik di mana nama kelas dasar dideklarasikan. Pertimbangkan deklarasi kelas berikut:
class MultInherit : public Base1, public Base2
...
Dalam contoh sebelumnya, destruktor untuk Base2
dipanggil sebelum destruktor untuk Base1
.
Panggilan destruktor eksplisit
Memanggil destruktor secara eksplisit jarang diperlukan. Namun, dapat berguna untuk melakukan pembersihan objek yang ditempatkan di alamat absolut. Objek ini biasanya dialokasikan menggunakan operator yang ditentukan new
pengguna yang mengambil argumen penempatan. Operator delete
tidak dapat membatalkan alokasi memori ini karena tidak dialokasikan dari penyimpanan gratis (untuk informasi selengkapnya, lihat Operator baru dan hapus). Namun, panggilan ke destructor dapat melakukan pembersihan yang sesuai. Untuk secara eksplisit memanggil destruktor untuk objek, s
, dari kelas String
, gunakan salah satu pernyataan berikut:
s.String::~String(); // non-virtual call
ps->String::~String(); // non-virtual call
s.~String(); // Virtual call
ps->~String(); // Virtual call
Notasi untuk panggilan eksplisit ke destruktor, yang ditunjukkan sebelumnya, dapat digunakan terlepas dari apakah jenis mendefinisikan destruktor. Ini memungkinkan Anda untuk melakukan panggilan eksplisit seperti itu tanpa mengetahui apakah destruktor didefinisikan untuk jenis tersebut. Panggilan eksplisit ke destruktor di mana tidak ada yang didefinisikan tidak berpengaruh.
Pemrograman yang kuat
Kelas membutuhkan destruktor jika memperoleh sumber daya, dan untuk mengelola sumber daya dengan aman mungkin harus menerapkan konstruktor salinan dan penugasan salinan.
Jika fungsi khusus ini tidak ditentukan oleh pengguna, fungsi tersebut secara implisit didefinisikan oleh pengkompilasi. Konstruktor dan operator penugasan yang dihasilkan secara implisit melakukan penyalinan dangkal dangkal, yang hampir pasti salah jika objek mengelola sumber daya.
Dalam contoh berikutnya, konstruktor salinan yang dihasilkan secara implisit akan membuat pointer str1.text
dan str2.text
merujuk ke memori yang sama, dan ketika kita kembali dari copy_strings()
, memori tersebut akan dihapus dua kali, yang merupakan perilaku yang tidak ditentukan:
void copy_strings()
{
String str1("I have a sense of impending disaster...");
String str2 = str1; // str1.text and str2.text now refer to the same object
} // delete[] _text; deallocates the same memory twice
// undefined behavior
Secara eksplisit mendefinisikan operator destruktor, konstruktor salinan, atau penugasan salin mencegah definisi implisit dari konstruktor pemindahan dan operator penetapan pemindahan. Dalam hal ini, gagal menyediakan operasi pemindahan biasanya, jika penyalinan mahal, peluang pengoptimalan yang terlewat.
Baca juga
Menyalin Konstruktor dan Menyalin Operator Penugasan
Pindahkan Konstruktor dan Pindahkan Operator Penugasan
Saran dan Komentar
https://aka.ms/ContentUserFeedback.
Segera hadir: Sepanjang tahun 2024 kami akan menghentikan penggunaan GitHub Issues sebagai mekanisme umpan balik untuk konten dan menggantinya dengan sistem umpan balik baru. Untuk mengetahui informasi selengkapnya, lihat:Kirim dan lihat umpan balik untuk