Bagikan melalui


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, , volatileatau static. Namun, mereka dapat dipanggil untuk penghancuran objek yang dinyatakan sebagai const, , volatileatau static.
  • 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 menggunakan new. Menggunakan delete[] hasil dalam perilaku yang tidak terdefinisi.
  • Gunakan delete[] untuk membatalkan alokasi objek yang dialokasikan menggunakan new[]. Menggunakan delete 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:

  1. Destruktor kelas dipanggil, dan isi fungsi destruktor dijalankan.

  2. 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.

  3. Destruktor untuk kelas dasar non-virtual dipanggil dalam urutan deklarasi terbalik.

  4. 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.

Inheritance graph that shows virtual base classes.

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:

  1. Melintasi grafik ke kiri, dimulai dari titik terdalam dalam grafik (dalam hal ini, E).
  2. Lakukan traversal ke kiri hingga semua simpul telah dikunjungi. Perhatikan nama simpul saat ini.
  3. Kunjungi kembali simpul sebelumnya (bawah dan ke kanan) untuk mengetahui apakah node yang diingat adalah kelas dasar virtual.
  4. Jika simpul yang diingat adalah kelas dasar virtual, pindai daftar untuk melihat apakah node tersebut telah dimasukkan. Jika bukan kelas dasar virtual, abaikan.
  5. Jika simpul yang diingat belum ada dalam daftar, tambahkan ke bagian bawah daftar.
  6. Melintasi grafik ke atas dan di sepanjang jalur berikutnya ke kanan.
  7. Buka langkah 2.
  8. Ketika jalur naik terakhir habis, perhatikan nama simpul saat ini.
  9. Buka langkah 3.
  10. Lanjutkan proses ini hingga simpul bawah lagi menjadi simpul saat ini.

Oleh karena itu, untuk kelas E, urutan penghancuran adalah:

  1. Kelas Edasar non-virtual .
  2. Kelas Ddasar non-virtual .
  3. Kelas Cdasar non-virtual .
  4. Kelas Bdasar virtual .
  5. Kelas Adasar 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