Dalam C ++ / CLI, yang disebut kelas deskriptor sering digunakan - kelas yang dikelola yang memiliki pointer ke kelas asli sebagai anggota. Artikel ini membahas skema yang mudah dan ringkas untuk mengelola masa pakai objek asli yang sesuai, berdasarkan penggunaan template yang dikelola. Kasus finalisasi yang kompleks dipertimbangkan.
Daftar isi
Pendahuluan
1. Pola Buang Dasar di C ++ / CLI
1.1. Definisi destruktor dan finalizer
1.2. Menggunakan tumpukan semantik
2. Template yang dikelola
2.1. Pointer pintar
2.2. Contoh penggunaan
2.3. Opsi finalisasi yang lebih kompleks
2.3.1. Kunci Finalizer
2.3.2. Menggunakan SafeHandle
Referensi
Pendahuluan
C ++ / CLI - salah satu bahasa dari .NET Framework - jarang digunakan untuk mengembangkan proyek independen besar. Tujuan utamanya adalah untuk membuat rakitan untuk interaksi .NET dengan kode asli (tidak dikelola). Dengan demikian, kelas yang disebut kelas deskriptor banyak digunakan, kelas yang dikelola yang memiliki pointer ke kelas asli sebagai anggota. Biasanya, kelas deskriptor tersebut memiliki objek asli yang sesuai, yaitu, ia harus menghapusnya pada waktu yang tepat. Sangat wajar untuk membuat kelas seperti itu dikecualikan, yaitu, mengimplementasikan System::IDisposable
. Implementasi antarmuka ini dalam .NET harus mengikuti pola khusus yang disebut Basic Buang [Cwalina]. Fitur luar biasa dari C ++ / CLI adalah bahwa kompiler melakukan hampir semua pekerjaan rutin penerapan templat ini, sementara di C # hampir semuanya harus dikerjakan dengan tangan.
1. Pola Buang Dasar di C ++ / CLI
Ada dua cara utama untuk mengimplementasikan template ini.
1.1. Definisi destruktor dan finalizer
Dalam hal ini, destructor dan finalizer harus didefinisikan dalam kelas yang dikelola, kompiler akan melakukan sisanya.
public ref class X { ~X() {}
Secara khusus, kompiler melakukan hal berikut:
- Untuk kelas
X
mengimplementasikan System::IDisposable
. - Di
X::Dispose()
menyediakan panggilan ke destruktor, panggilan ke destruktor dari kelas dasar (jika ada), dan panggilan ke GC::SupressFinalize()
. - Overrides
System::Object::Finalize()
, di mana ia menyediakan panggilan ke finalizer dan finalizers dari kelas dasar (jika ada).
Anda dapat menentukan warisan dari System::IDisposable
secara eksplisit, tetapi Anda tidak dapat menentukan sendiri X::Dispose()
.
1.2. Menggunakan tumpukan semantik
Pola Buang Dasar juga diterapkan oleh kompiler jika kelas memiliki anggota dari tipe yang dibebaskan dan dideklarasikan menggunakan stack semantik. Ini berarti bahwa nama tipe tanpa sampul (' ^
') digunakan untuk deklarasi, dan inisialisasi terjadi dalam daftar inisialisasi konstruktor, dan tidak menggunakan gcnew
. Semantik tumpukan dijelaskan dalam [Hogenson].
Berikut ini sebuah contoh:
public ref class R : System::IDisposable { public: R();
Kompiler dalam hal ini melakukan hal berikut:
- Untuk kelas
X
mengimplementasikan System::IDisposable
. - Di
X::Dispose()
m_R
panggilan ke R::Dispose()
untuk m_R
.
Finalisasi ditentukan oleh fungsionalitas kelas R
sesuai. Seperti dalam kasus sebelumnya, warisan dari System::IDisposable
dapat ditentukan secara eksplisit, tetapi Anda tidak dapat mendefinisikan X::Dispose()
sendiri. Tentu saja, kelas mungkin memiliki anggota lain dinyatakan menggunakan semantik stack, dan panggilan Dispose()
mereka juga disediakan untuk mereka.
2. Template yang dikelola
Dan akhirnya, fitur hebat lain dari C ++ / CLI memungkinkan untuk menyederhanakan pembuatan kelas deskriptor sebanyak mungkin. Kita berbicara tentang template yang dikelola. Ini bukan generik, tetapi templat nyata, seperti pada C ++ klasik, tetapi templat bukan asli, tetapi kelas yang dikelola. Instansiasi pola tersebut mengarah pada penciptaan kelas yang dikelola yang dapat digunakan sebagai kelas dasar atau sebagai anggota kelas lain dalam suatu majelis. Templat yang dikelola dijelaskan dalam [Hogenson].
2.1. Pointer pintar
Template terkelola memungkinkan Anda membuat kelas seperti pointer pintar yang berisi pointer ke objek asli sebagai anggota dan menyediakan penghapusannya di destruktor dan finalizer. Pointer pintar tersebut dapat digunakan sebagai kelas dasar atau anggota (secara alami, menggunakan stack semantik) ketika mengembangkan kelas deskriptor yang secara otomatis menjadi dibebaskan.
Berikut adalah contoh dari pola tersebut. Template pertama adalah template dasar, yang kedua dimaksudkan untuk digunakan sebagai kelas dasar, dan yang ketiga sebagai anggota kelas. Templat ini memiliki parameter templat (asli) yang dirancang untuk menghapus objek. Kelas penghapusan, secara default, menghapus objek dengan operator delete
.
2.2. Contoh penggunaan
class N // { public: N(); ~N(); void DoSomething();
Dalam contoh-contoh ini, kelas U
dan V
menjadi dibebaskan tanpa upaya tambahan; Dispose()
mereka Dispose()
memberikan panggilan ke operator delete
untuk sebuah pointer ke N
Opsi kedua, menggunakan ImplPtrM<>
, memungkinkan Anda untuk mengelola beberapa kelas asli dalam satu kelas deskriptor tunggal.
2.3. Opsi finalisasi yang lebih kompleks
Finalisasi adalah aspek yang agak bermasalah dari .NET. Dalam skenario aplikasi normal, penyelesai tidak boleh dipanggil; sumber daya harus dirilis di Dispose()
. Tetapi dalam skenario darurat ini bisa terjadi dan finalizers harus bekerja dengan benar.
2.3.1. Kunci Finalizer
Jika kelas asli terletak di DLL yang memuat dan membongkar secara dinamis - menggunakan LoadLibrary()/FreeLibrary()
, maka situasi dapat muncul ketika setelah membongkar DLL ada objek yang belum dirilis yang memiliki referensi ke instance dari kelas ini. Dalam hal ini, setelah beberapa saat pemulung akan mencoba menyelesaikannya, dan karena DLL diturunkan, program kemungkinan besar akan macet. (Fitur karakteristik adalah crash beberapa detik setelah aplikasi tampaknya ditutup.) Oleh karena itu, setelah membongkar DLL, penyelesai harus diblokir. Ini dapat dicapai dengan sedikit modifikasi templat ImplPtrBase
dasar.
public ref class DllFlag { protected: static bool s_Loaded = false; public: static void SetLoaded(bool loaded) { s_Loaded = loaded; } }; template <typename T, typename D> public ref class ImplPtrBase : DllFlag, System::IDisposable {
Setelah memuat DLL, Anda perlu memanggil DllFlag::SetLoaded(true)
, dan sebelum membongkar DllFlag::SetLoaded(false)
.
2.3.2. Menggunakan SafeHandle
Kelas SafeHandle
mengimplementasikan algoritma finalisasi yang agak rumit dan paling dapat diandalkan, lihat [Richter]. ImplPtrBase<>
dapat dirancang ulang untuk menggunakan SafeHandle
. Templat yang tersisa tidak perlu diubah.
using SH = System::Runtime::InteropServices::SafeHandle; using PtrType = System::IntPtr; template <typename T, typename D> public ref class ImplPtrBase : SH { protected: ImplPtrBase(T* p) : SH(PtrType::Zero, true) { handle = PtrType(p); } T* Ptr() { return static_cast<T*>(handle.ToPointer()); } bool ReleaseHandle() override { if (!IsInvalid) { D del; del(Ptr()); handle = PtrType::Zero; } return true; } public: property bool IsInvalid { bool get() override { return (handle == PtrType::Zero); } } };
Referensi
[Lebih kaya]
Richter, Jeffrey. Pemrograman pada platform Microsoft .NET Framework 4.5 di C #. Edisi ke-4: Per. dari bahasa inggris - St. Petersburg: Peter, 2016.
[Cwalina]
Tsvalina, Krzhishtov. Abrams, Brad. Infrastruktur proyek perangkat lunak: konvensi, idiom, dan templat untuk pustaka .NET yang dapat digunakan kembali.: Terjemahan. dari bahasa inggris - M.: LLC βSaya. Williams, 2011.
[Hogenson]
Hogenson, Gordon. C ++ / CLI: Bahasa Visual C ++ untuk lingkungan .NET: Per. dari bahasa inggris - M.: LLC βSaya. Williams, 2007.