Bagikan melalui


Cara: Menentukan dan menggunakan kelas dan struktur (C++/CLI)

Artikel ini memperlihatkan cara menentukan dan menggunakan jenis referensi dan jenis nilai yang ditentukan pengguna di C++/CLI.

Instansiasi objek

Jenis referensi (ref) hanya dapat dibuat pada tumpukan terkelola, bukan pada tumpukan atau pada tumpukan asli. Jenis nilai dapat diinstansiasi pada tumpukan atau tumpukan terkelola.

// mcppv2_ref_class2.cpp
// compile with: /clr
ref class MyClass {
public:
   int i;

   // nested class
   ref class MyClass2 {
   public:
      int i;
   };

   // nested interface
   interface struct MyInterface {
      void f();
   };
};

ref class MyClass2 : public MyClass::MyInterface {
public:
   virtual void f() {
      System::Console::WriteLine("test");
   }
};

public value struct MyStruct {
   void f() {
      System::Console::WriteLine("test");
   }
};

int main() {
   // instantiate ref type on garbage-collected heap
   MyClass ^ p_MyClass = gcnew MyClass;
   p_MyClass -> i = 4;

   // instantiate value type on garbage-collected heap
   MyStruct ^ p_MyStruct = gcnew MyStruct;
   p_MyStruct -> f();

   // instantiate value type on the stack
   MyStruct p_MyStruct2;
   p_MyStruct2.f();

   // instantiate nested ref type on garbage-collected heap
   MyClass::MyClass2 ^ p_MyClass2 = gcnew MyClass::MyClass2;
   p_MyClass2 -> i = 5;
}

Kelas abstrak secara implisit

Kelas abstrak implisit tidak dapat diinstansiasi. Kelas secara implisit mengabstraksi ketika:

  • jenis dasar kelas adalah antarmuka, dan
  • kelas tidak mengimplementasikan semua fungsi anggota antarmuka.

Anda mungkin tidak dapat membuat objek dari kelas yang berasal dari antarmuka. Alasannya mungkin karena kelas tersebut secara implisit abstrak. Untuk informasi selengkapnya tentang kelas abstrak, lihat abstrak.

Contoh kode berikut menunjukkan bahwa MyClass kelas tidak dapat diinstansiasi karena fungsi MyClass::func2 tidak diimplementasikan. Untuk mengaktifkan contoh untuk mengkompilasi, batalkan komentar MyClass::func2.

// mcppv2_ref_class5.cpp
// compile with: /clr
interface struct MyInterface {
   void func1();
   void func2();
};

ref class MyClass : public MyInterface {
public:
   void func1(){}
   // void func2(){}
};

int main() {
   MyClass ^ h_MyClass = gcnew MyClass;   // C2259
                                          // To resolve, uncomment MyClass::func2.
}

Visibilitas jenis

Anda dapat mengontrol visibilitas jenis runtime bahasa umum (CLR). Saat rakitan Anda dirujuk, Anda mengontrol apakah jenis dalam rakitan terlihat atau tidak terlihat di luar rakitan.

public menunjukkan bahwa jenis terlihat oleh file sumber apa pun yang berisi #using direktif untuk rakitan yang berisi jenis . private menunjukkan bahwa jenis tidak terlihat oleh file sumber yang berisi #using direktif untuk rakitan yang berisi jenis . Namun, jenis privat terlihat dalam rakitan yang sama. Secara default, visibilitas untuk kelas adalah private.

Secara default sebelum Visual Studio 2005, jenis asli memiliki aksesibilitas publik di luar rakitan. Aktifkan Compiler Warning (level 1) C4692 untuk membantu Anda melihat di mana jenis asli privat salah digunakan. Gunakan pragma make_public untuk memberikan aksesibilitas publik ke jenis asli dalam file kode sumber yang tidak dapat Anda ubah.

Untuk informasi selengkapnya, lihat Direktif #using.

Sampel berikut menunjukkan cara mendeklarasikan jenis dan menentukan aksesibilitasnya, lalu mengakses jenis tersebut di dalam rakitan. Jika rakitan yang memiliki jenis privat dirujuk dengan menggunakan #using, hanya jenis publik di rakitan yang terlihat.

// type_visibility.cpp
// compile with: /clr
using namespace System;
// public type, visible inside and outside assembly
public ref struct Public_Class {
   void Test(){Console::WriteLine("in Public_Class");}
};

// private type, visible inside but not outside assembly
private ref struct Private_Class {
   void Test(){Console::WriteLine("in Private_Class");}
};

// default accessibility is private
ref class Private_Class_2 {
public:
   void Test(){Console::WriteLine("in Private_Class_2");}
};

int main() {
   Public_Class ^ a = gcnew Public_Class;
   a->Test();

   Private_Class ^ b = gcnew Private_Class;
   b->Test();

   Private_Class_2 ^ c = gcnew Private_Class_2;
   c->Test();
}

Hasil

in Public_Class
in Private_Class
in Private_Class_2

Sekarang, mari kita tulis ulang sampel sebelumnya sehingga dibuat sebagai DLL.

// type_visibility_2.cpp
// compile with: /clr /LD
using namespace System;
// public type, visible inside and outside the assembly
public ref struct Public_Class {
   void Test(){Console::WriteLine("in Public_Class");}
};

// private type, visible inside but not outside the assembly
private ref struct Private_Class {
   void Test(){Console::WriteLine("in Private_Class");}
};

// by default, accessibility is private
ref class Private_Class_2 {
public:
   void Test(){Console::WriteLine("in Private_Class_2");}
};

Sampel berikutnya menunjukkan cara mengakses jenis di luar rakitan. Dalam sampel ini, klien menggunakan komponen yang dibangun dalam sampel sebelumnya.

// type_visibility_3.cpp
// compile with: /clr
#using "type_visibility_2.dll"
int main() {
   Public_Class ^ a = gcnew Public_Class;
   a->Test();

   // private types not accessible outside the assembly
   // Private_Class ^ b = gcnew Private_Class;
   // Private_Class_2 ^ c = gcnew Private_Class_2;
}

Hasil

in Public_Class

Visibilitas anggota

Anda dapat membuat akses ke anggota kelas publik dari dalam rakitan yang sama berbeda dari akses ke kelas publik dari luar rakitan dengan menggunakan pasangan penentu publicakses , , protecteddan private

Tabel ini meringkas efek dari berbagai penentu akses:

Penentu Efek
public Anggota dapat diakses di dalam dan di luar rakitan. Untuk informasi selengkapnya, lihat public .
private Anggota tidak dapat diakses, baik di dalam maupun di luar perakitan. Untuk informasi selengkapnya, lihat private .
protected Anggota dapat diakses di dalam dan di luar rakitan, tetapi hanya untuk jenis turunan. Untuk informasi selengkapnya, lihat protected .
internal Anggota adalah publik di dalam rakitan tetapi privat di luar perakitan. internal adalah kata kunci yang sensitif terhadap konteks. Untuk informasi selengkapnya, lihat Kata Kunci Peka Konteks.
public protected -Atau- protected public Anggota bersifat publik di dalam rakitan tetapi dilindungi di luar rakitan.
private protected -Atau- protected private Anggota dilindungi di dalam rakitan tetapi privat di luar perakitan.

Sampel berikut menunjukkan jenis publik yang memiliki anggota yang dinyatakan menggunakan penentu akses yang berbeda. Kemudian, ini menunjukkan akses ke anggota tersebut dari dalam perakitan.

// compile with: /clr
using namespace System;
// public type, visible inside and outside the assembly
public ref class Public_Class {
public:
   void Public_Function(){System::Console::WriteLine("in Public_Function");}

private:
   void Private_Function(){System::Console::WriteLine("in Private_Function");}

protected:
   void Protected_Function(){System::Console::WriteLine("in Protected_Function");}

internal:
   void Internal_Function(){System::Console::WriteLine("in Internal_Function");}

protected public:
   void Protected_Public_Function(){System::Console::WriteLine("in Protected_Public_Function");}

public protected:
   void Public_Protected_Function(){System::Console::WriteLine("in Public_Protected_Function");}

private protected:
   void Private_Protected_Function(){System::Console::WriteLine("in Private_Protected_Function");}

protected private:
   void Protected_Private_Function(){System::Console::WriteLine("in Protected_Private_Function");}
};

// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
   void Test() {
      Console::WriteLine("=======================");
      Console::WriteLine("in function of derived class");
      Protected_Function();
      Protected_Private_Function();
      Private_Protected_Function();
      Console::WriteLine("exiting function of derived class");
      Console::WriteLine("=======================");
   }
};

int main() {
   Public_Class ^ a = gcnew Public_Class;
   MyClass ^ b = gcnew MyClass;
   a->Public_Function();
   a->Protected_Public_Function();
   a->Public_Protected_Function();

   // accessible inside but not outside the assembly
   a->Internal_Function();

   // call protected functions
   b->Test();

   // not accessible inside or outside the assembly
   // a->Private_Function();
}

Hasil

in Public_Function
in Protected_Public_Function
in Public_Protected_Function
in Internal_Function
=======================
in function of derived class
in Protected_Function
in Protected_Private_Function
in Private_Protected_Function
exiting function of derived class
=======================

Sekarang mari kita buat sampel sebelumnya sebagai DLL.

// compile with: /clr /LD
using namespace System;
// public type, visible inside and outside the assembly
public ref class Public_Class {
public:
   void Public_Function(){System::Console::WriteLine("in Public_Function");}

private:
   void Private_Function(){System::Console::WriteLine("in Private_Function");}

protected:
   void Protected_Function(){System::Console::WriteLine("in Protected_Function");}

internal:
   void Internal_Function(){System::Console::WriteLine("in Internal_Function");}

protected public:
   void Protected_Public_Function(){System::Console::WriteLine("in Protected_Public_Function");}

public protected:
   void Public_Protected_Function(){System::Console::WriteLine("in Public_Protected_Function");}

private protected:
   void Private_Protected_Function(){System::Console::WriteLine("in Private_Protected_Function");}

protected private:
   void Protected_Private_Function(){System::Console::WriteLine("in Protected_Private_Function");}
};

// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
   void Test() {
      Console::WriteLine("=======================");
      Console::WriteLine("in function of derived class");
      Protected_Function();
      Protected_Private_Function();
      Private_Protected_Function();
      Console::WriteLine("exiting function of derived class");
      Console::WriteLine("=======================");
   }
};

Sampel berikut mengonsumsi komponen yang dibuat dalam sampel sebelumnya. Ini menunjukkan cara mengakses anggota dari luar assembly.

// compile with: /clr
#using "type_member_visibility_2.dll"
using namespace System;
// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
   void Test() {
      Console::WriteLine("=======================");
      Console::WriteLine("in function of derived class");
      Protected_Function();
      Protected_Public_Function();
      Public_Protected_Function();
      Console::WriteLine("exiting function of derived class");
      Console::WriteLine("=======================");
   }
};

int main() {
   Public_Class ^ a = gcnew Public_Class;
   MyClass ^ b = gcnew MyClass;
   a->Public_Function();

   // call protected functions
   b->Test();

   // can't be called outside the assembly
   // a->Private_Function();
   // a->Internal_Function();
   // a->Protected_Private_Function();
   // a->Private_Protected_Function();
}

Hasil

in Public_Function
=======================
in function of derived class
in Protected_Function
in Protected_Public_Function
in Public_Protected_Function
exiting function of derived class
=======================

Kelas asli publik dan privat

Jenis asli dapat dirujuk dari jenis terkelola. Misalnya, fungsi dalam jenis terkelola dapat mengambil parameter yang jenisnya adalah struct asli. Jika jenis dan fungsi terkelola bersifat publik dalam rakitan, maka jenis aslinya juga harus publik.

// native type
public struct N {
   N(){}
   int i;
};

Selanjutnya, buat file kode sumber yang menggunakan jenis asli:

// compile with: /clr /LD
#include "mcppv2_ref_class3.h"
// public managed type
public ref struct R {
   // public function that takes a native type
   void f(N nn) {}
};

Sekarang, kompilasi klien:

// compile with: /clr
#using "mcppv2_ref_class3.dll"

#include "mcppv2_ref_class3.h"

int main() {
   R ^r = gcnew R;
   N n;
   r->f(n);
}

Konstruktor statik

Jenis CLR—misalnya, kelas atau struktur—dapat memiliki konstruktor statis yang dapat digunakan untuk menginisialisasi anggota data statis. Konstruktor statis dipanggil paling banyak sekali, dan dipanggil sebelum anggota statis jenis diakses pertama kali.

Konstruktor instans selalu berjalan setelah konstruktor statis.

Pengkompilasi tidak dapat menginline panggilan ke konstruktor jika kelas memiliki konstruktor statis. Pengkompilasi tidak dapat melakukan panggilan sebaris ke fungsi anggota apa pun jika kelas adalah jenis nilai, memiliki konstruktor statis, dan tidak memiliki konstruktor instans. CLR mungkin menginline panggilan, tetapi pengkompilasi tidak dapat.

Tentukan konstruktor statis sebagai fungsi anggota privat, karena dimaksudkan untuk dipanggil hanya oleh CLR.

Untuk informasi selengkapnya tentang konstruktor statis, lihat Cara: Menentukan Konstruktor Statis Antarmuka (C++/CLI) .

// compile with: /clr
using namespace System;

ref class MyClass {
private:
   static int i = 0;

   static MyClass() {
      Console::WriteLine("in static constructor");
      i = 9;
   }

public:
   static void Test() {
      i++;
      Console::WriteLine(i);
   }
};

int main() {
   MyClass::Test();
   MyClass::Test();
}

Hasil

in static constructor
10
11

Semantik penunjuk this

Saat Anda menggunakan C++\CLI untuk menentukan jenis, this penunjuk dalam jenis referensi berjenis handel. Penunjuk this dalam jenis nilai berjenis penunjuk interior.

Semantik penunjuk yang this berbeda ini dapat menyebabkan perilaku tak terduga ketika pengindeks default dipanggil. Contoh berikutnya menunjukkan cara yang benar untuk mengakses pengindeks default dalam jenis ref dan jenis nilai.

Untuk informasi selengkapnya, lihat Menangani ke Operator Objek (^) dan interior_ptr (C++/CLI)

// compile with: /clr
using namespace System;

ref struct A {
   property Double default[Double] {
      Double get(Double data) {
         return data*data;
      }
   }

   A() {
      // accessing default indexer
      Console::WriteLine("{0}", this[3.3]);
   }
};

value struct B {
   property Double default[Double] {
      Double get(Double data) {
         return data*data;
      }
   }
   void Test() {
      // accessing default indexer
      Console::WriteLine("{0}", this->default[3.3]);
   }
};

int main() {
   A ^ mya = gcnew A();
   B ^ myb = gcnew B();
   myb->Test();
}

Hasil

10.89
10.89

Fungsi hide-by-signature

Dalam C++standar, fungsi di kelas dasar disembunyikan oleh fungsi yang memiliki nama yang sama di kelas turunan, bahkan jika fungsi kelas turunan tidak memiliki jenis atau jumlah parameter yang sama. Ini dikenal sebagai semantik hide-by-name . Dalam jenis referensi, fungsi di kelas dasar hanya disembunyikan oleh fungsi di kelas turunan jika nama dan daftar parameter sama. Ini dikenal sebagai semantik hide-by-signature .

Kelas dianggap sebagai kelas hide-by-signature ketika semua fungsinya ditandai dalam metadata sebagai hidebysig. Secara default, semua kelas yang dibuat di bawah /clr memiliki hidebysig fungsi. Ketika kelas memiliki hidebysig fungsi, pengkompilasi tidak menyembunyikan fungsi berdasarkan nama di kelas dasar langsung apa pun, tetapi jika kompilator menemukan kelas hide-by-name dalam rantai pewarisan, ia melanjutkan perilaku hide-by-name tersebut.

Di bawah semantik hide-by-signature, ketika fungsi dipanggil pada objek, pengkompilasi mengidentifikasi kelas yang paling turunan yang berisi fungsi yang dapat memenuhi panggilan fungsi. Jika hanya ada satu fungsi di kelas yang memenuhi panggilan, pengkompilasi memanggil fungsi tersebut. Jika ada lebih dari satu fungsi di kelas yang dapat memenuhi panggilan, pengkompilasi menggunakan aturan resolusi kelebihan beban untuk menentukan fungsi mana yang akan dipanggil. Untuk informasi selengkapnya tentang aturan kelebihan beban, lihat Kelebihan Beban Fungsi.

Untuk panggilan fungsi tertentu, fungsi di kelas dasar mungkin memiliki tanda tangan yang membuatnya sedikit lebih cocok daripada fungsi di kelas turunan. Namun, jika fungsi secara eksplisit dipanggil pada objek kelas turunan, fungsi di kelas turunan dipanggil.

Karena nilai pengembalian tidak dianggap sebagai bagian dari tanda tangan fungsi, fungsi kelas dasar akan disembunyikan jika memiliki nama yang sama dan mengambil jenis dan jumlah argumen yang sama sebagai fungsi kelas turunan, bahkan jika berbeda dalam jenis nilai pengembalian.

Sampel berikut menunjukkan bahwa fungsi di kelas dasar tidak disembunyikan oleh fungsi di kelas turunan.

// compile with: /clr
using namespace System;
ref struct Base {
   void Test() {
      Console::WriteLine("Base::Test");
   }
};

ref struct Derived : public Base {
   void Test(int i) {
      Console::WriteLine("Derived::Test");
   }
};

int main() {
   Derived ^ t = gcnew Derived;
   // Test() in the base class will not be hidden
   t->Test();
}

Hasil

Base::Test

Sampel berikutnya menunjukkan bahwa pengkompilasi Microsoft C++ memanggil fungsi di kelas yang paling turunan—bahkan jika konversi diperlukan untuk mencocokkan satu atau beberapa parameter—dan tidak memanggil fungsi di kelas dasar yang lebih cocok untuk panggilan fungsi.

// compile with: /clr
using namespace System;
ref struct Base {
   void Test2(Single d) {
      Console::WriteLine("Base::Test2");
   }
};

ref struct Derived : public Base {
   void Test2(Double f) {
      Console::WriteLine("Derived::Test2");
   }
};

int main() {
   Derived ^ t = gcnew Derived;
   // Base::Test2 is a better match, but the compiler
   // calls a function in the derived class if possible
   t->Test2(3.14f);
}

Hasil

Derived::Test2

Sampel berikut menunjukkan bahwa dimungkinkan untuk menyembunyikan fungsi meskipun kelas dasar memiliki tanda tangan yang sama dengan kelas turunan.

// compile with: /clr
using namespace System;
ref struct Base {
   int Test4() {
      Console::WriteLine("Base::Test4");
      return 9;
   }
};

ref struct Derived : public Base {
   char Test4() {
      Console::WriteLine("Derived::Test4");
      return 'a';
   }
};

int main() {
   Derived ^ t = gcnew Derived;

   // Base::Test4 is hidden
   int i = t->Test4();
   Console::WriteLine(i);
}

Hasil

Derived::Test4
97

Menyalin konstruktor

Standar C++ mengatakan bahwa konstruktor salinan dipanggil ketika objek dipindahkan, sehingga objek dibuat dan dihancurkan pada alamat yang sama.

Namun, ketika fungsi yang dikompilasi ke MSIL memanggil fungsi asli di mana kelas asli—atau lebih dari satu—diteruskan oleh nilai dan di mana kelas asli memiliki konstruktor salinan atau destruktor, tidak ada konstruktor salinan yang dipanggil dan objek dihancurkan pada alamat yang berbeda dari tempatnya dibuat. Perilaku ini dapat menyebabkan masalah jika kelas memiliki penunjuk ke dalam dirinya sendiri, atau jika kode melacak objek berdasarkan alamat.

Untuk informasi selengkapnya, lihat /clr (Kompilasi Runtime Bahasa Umum).

Sampel berikut menunjukkan kapan konstruktor salinan tidak dihasilkan.

// compile with: /clr
#include<stdio.h>

struct S {
   int i;
   static int n;

   S() : i(n++) {
      printf_s("S object %d being constructed, this=%p\n", i, this);
   }

   S(S const& rhs) : i(n++) {
      printf_s("S object %d being copy constructed from S object "
               "%d, this=%p\n", i, rhs.i, this);
   }

   ~S() {
      printf_s("S object %d being destroyed, this=%p\n", i, this);
   }
};

int S::n = 0;

#pragma managed(push,off)
void f(S s1, S s2) {
   printf_s("in function f\n");
}
#pragma managed(pop)

int main() {
   S s;
   S t;
   f(s,t);
}

Hasil

S object 0 being constructed, this=0018F378
S object 1 being constructed, this=0018F37C
S object 2 being copy constructed from S object 1, this=0018F380
S object 3 being copy constructed from S object 0, this=0018F384
S object 4 being copy constructed from S object 2, this=0018F2E4
S object 2 being destroyed, this=0018F380
S object 5 being copy constructed from S object 3, this=0018F2E0
S object 3 being destroyed, this=0018F384
in function f
S object 5 being destroyed, this=0018F2E0
S object 4 being destroyed, this=0018F2E4
S object 1 being destroyed, this=0018F37C
S object 0 being destroyed, this=0018F378

Destruktor dan finalizer

Destruktor dalam jenis referensi melakukan pembersihan sumber daya yang deterministik. Finalizer membersihkan sumber daya yang tidak dikelola, dan dapat dipanggil secara deterministik oleh destruktor atau nondeterministik oleh pengumpul sampah. Untuk informasi tentang destruktor di C++standar, lihat Destruktor.

class classname {
   ~classname() {}   // destructor
   ! classname() {}   // finalizer
};

Pengumpul sampah CLR menghapus objek terkelola yang tidak digunakan dan melepaskan memori mereka saat tidak lagi diperlukan. Namun, jenis dapat menggunakan sumber daya yang tidak diketahui oleh pengumpul sampah. Sumber daya ini dikenal sebagai sumber daya yang tidak dikelola (handel file asli, misalnya). Kami sarankan Anda merilis semua sumber daya yang tidak dikelola di finalizer. Pengumpul sampah merilis sumber daya terkelola secara nondeterministik, sehingga tidak aman untuk merujuk ke sumber daya terkelola di finalizer. Itu karena ada kemungkinan pengumpul sampah telah membersihkannya.

Finalizer Visual C++ tidak sama Finalize dengan metode . (Dokumentasi CLR menggunakan finalizer dan Finalize metode secara sinonim). Metode Finalize ini disebut oleh pengumpul sampah, yang memanggil setiap finalizer dalam rantai warisan kelas. Tidak seperti destruktor Visual C++, panggilan finalizer kelas turunan tidak menyebabkan pengkompilasi memanggil finalizer di semua kelas dasar.

Karena pengkompilasi Microsoft C++ mendukung rilis sumber daya deterministik, jangan mencoba menerapkan Dispose metode atau Finalize . Namun, jika Anda terbiasa dengan metode ini, berikut adalah bagaimana finalizer Visual C++ dan destruktor yang memanggil peta finalizer ke Dispose pola:

// Visual C++ code
ref class T {
   ~T() { this->!T(); }   // destructor calls finalizer
   !T() {}   // finalizer
};

// equivalent to the Dispose pattern
void Dispose(bool disposing) {
   if (disposing) {
      ~T();
   } else {
      !T();
   }
}

Jenis terkelola juga dapat menggunakan sumber daya terkelola yang lebih Anda sukai untuk dirilis secara deterministik. Anda mungkin tidak ingin pengumpul sampah melepaskan objek secara nondeterministik pada beberapa titik setelah objek tidak lagi diperlukan. Rilis deterministik sumber daya dapat secara signifikan meningkatkan performa.

Pengkompilasi Microsoft C++ memungkinkan definisi destruktor untuk membersihkan objek secara deterministik. Gunakan destruktor untuk merilis semua sumber daya yang ingin Anda rilis secara deterministik. Jika ada finalizer, panggil dari destruktor, untuk menghindari duplikasi kode.

// compile with: /clr /c
ref struct A {
   // destructor cleans up all resources
   ~A() {
      // clean up code to release managed resource
      // ...
      // to avoid code duplication,
      // call finalizer to release unmanaged resources
      this->!A();
   }

   // finalizer cleans up unmanaged resources
   // destructor or garbage collector will
   // clean up managed resources
   !A() {
      // clean up code to release unmanaged resources
      // ...
   }
};

Jika kode yang menggunakan jenis Anda tidak memanggil destruktor, pengumpul sampah akhirnya merilis semua sumber daya terkelola.

Kehadiran destruktor tidak menyiratkan adanya finalizer. Namun, kehadiran finalizer menyiratkan bahwa Anda harus menentukan destruktor dan memanggil finalizer dari destruktor itu. Panggilan ini menyediakan rilis deterministik sumber daya yang tidak dikelola.

Memanggil destruktor menekan—dengan menggunakan SuppressFinalize—finalisasi objek. Jika destruktor tidak dipanggil, finalizer jenis Anda akhirnya akan dipanggil oleh pengumpul sampah.

Anda dapat meningkatkan performa dengan memanggil destruktor untuk secara deterministik membersihkan sumber daya objek Anda, alih-alih membiarkan CLR secara nondeterministik menyelesaikan objek.

Kode yang ditulis dalam Visual C++ dan dikompilasi dengan menggunakan /clr destruktor jenis jika:

  • Objek yang dibuat dengan menggunakan semantik tumpukan keluar dari cakupan. Untuk informasi selengkapnya, lihat C++ Stack Semantics untuk Jenis Referensi.

  • Pengecualian dilemparkan selama konstruksi objek.

  • Objek adalah anggota dalam objek yang destruktornya sedang berjalan.

  • Anda memanggil operator penghapusan pada handel (Handel ke Operator Objek (^)).

  • Anda secara eksplisit memanggil destruktor.

Jika klien yang ditulis dalam bahasa lain menggunakan jenis Anda, destruktor akan dipanggil sebagai berikut:

  • Pada panggilan ke Dispose.

  • Pada panggilan ke Dispose(void) pada jenis.

  • Jika jenis keluar dari cakupan dalam pernyataan C using #.

Jika Anda tidak menggunakan semantik tumpukan untuk jenis referensi dan membuat objek jenis referensi pada tumpukan terkelola, gunakan sintaks try-finally untuk memastikan bahwa pengecualian tidak mencegah destruktor berjalan.

// compile with: /clr
ref struct A {
   ~A() {}
};

int main() {
   A ^ MyA = gcnew A;
   try {
      // use MyA
   }
   finally {
      delete MyA;
   }
}

Jika jenis Anda memiliki destruktor, pengkompilasi menghasilkan Dispose metode yang mengimplementasikan IDisposable. Jika jenis yang ditulis dalam Visual C++ dan memiliki destruktor yang dikonsumsi dari bahasa lain, memanggil IDisposable::Dispose jenis tersebut menyebabkan destruktor jenis dipanggil. Ketika jenis dikonsumsi dari klien Visual C++, Anda tidak dapat langsung memanggil Dispose; sebagai gantinya, panggil destruktor dengan menggunakan delete operator.

Jika jenis Anda memiliki finalizer, pengkompilasi menghasilkan Finalize(void) metode yang mengambil alih Finalize.

Jika jenis memiliki finalizer atau destruktor, pengkompilasi menghasilkan Dispose(bool) metode, sesuai dengan pola desain. (Untuk informasi, lihat Buang Pola). Anda tidak dapat secara eksplisit menulis atau memanggil Dispose(bool) di Visual C++.

Jika jenis memiliki kelas dasar yang sesuai dengan pola desain, destruktor untuk semua kelas dasar dipanggil ketika destruktor untuk kelas turunan dipanggil. (Jika jenis Anda ditulis dalam Visual C++, pengkompilasi memastikan bahwa jenis Anda menerapkan pola ini.) Dengan kata lain, destruktor rantai kelas referensi ke basis dan anggotanya sebagaimana ditentukan oleh standar C++. Pertama, destruktor kelas dijalankan. Kemudian, para destruktor untuk anggotanya dijalankan dalam urutan terbalik di mana mereka dibangun. Akhirnya, destruktor untuk kelas dasarnya dijalankan dalam urutan terbalik di mana mereka dibangun.

Destruktor dan finalizer tidak diizinkan di dalam jenis nilai atau antarmuka.

Finalizer hanya dapat ditentukan atau dideklarasikan dalam jenis referensi. Seperti konstruktor dan destruktor, finalizer tidak memiliki jenis pengembalian.

Setelah finalizer objek berjalan, finalizer di kelas dasar apa pun juga dipanggil, dimulai dengan jenis yang paling tidak diturunkan. Finalizer untuk anggota data tidak secara otomatis ditautkan oleh finalizer kelas.

Jika finalizer menghapus pointer asli dalam jenis terkelola, Anda harus memastikan bahwa referensi ke atau melalui pointer asli tidak dikumpulkan secara prematur. Panggil destruktor pada jenis terkelola alih-alih menggunakan KeepAlive.

Pada waktu kompilasi, Anda dapat mendeteksi apakah jenis memiliki finalizer atau destruktor. Untuk informasi selengkapnya, lihat Dukungan Pengkompilasi untuk Jenis Sifat.

Sampel berikutnya menunjukkan dua jenis: satu yang memiliki sumber daya yang tidak dikelola, dan satu yang memiliki sumber daya terkelola yang dirilis secara deterministik.

// compile with: /clr
#include <vcclr.h>
#include <stdio.h>
using namespace System;
using namespace System::IO;

ref class SystemFileWriter {
   FileStream ^ file;
   array<Byte> ^ arr;
   int bufLen;

public:
   SystemFileWriter(String ^ name) : file(File::Open(name, FileMode::Append)),
                                     arr(gcnew array<Byte>(1024)) {}

   void Flush() {
      file->Write(arr, 0, bufLen);
      bufLen = 0;
   }

   ~SystemFileWriter() {
      Flush();
      delete file;
   }
};

ref class CRTFileWriter {
   FILE * file;
   array<Byte> ^ arr;
   int bufLen;

   static FILE * getFile(String ^ n) {
      pin_ptr<const wchar_t> name = PtrToStringChars(n);
      FILE * ret = 0;
      _wfopen_s(&ret, name, L"ab");
      return ret;
   }

public:
   CRTFileWriter(String ^ name) : file(getFile(name)), arr(gcnew array<Byte>(1024) ) {}

   void Flush() {
      pin_ptr<Byte> buf = &arr[0];
      fwrite(buf, 1, bufLen, file);
      bufLen = 0;
   }

   ~CRTFileWriter() {
      this->!CRTFileWriter();
   }

   !CRTFileWriter() {
      Flush();
      fclose(file);
   }
};

int main() {
   SystemFileWriter w("systest.txt");
   CRTFileWriter ^ w2 = gcnew CRTFileWriter("crttest.txt");
}

Baca juga

Kelas dan struktur