Aturan untuk Mengelola Jumlah Referensi

Tip

Gunakan penunjuk cerdas untuk mengotomatiskan AddRef/Release. Penghitungan referensi manual rawan kesalahan. Kode C++ COM modern harus menggunakan pointer pintar yang memanggil AddRef dan Release secara otomatis:

Penunjuk cerdas Library Kapan digunakan
Microsoft::WRL::ComPtr<T> Pustaka Templat Windows Runtime C++ (WRL) WinRT dan COM klasik di C++
winrt::com_ptr<T> C++/WinRT Proyek C++/WinRT (lebih disukai untuk kode baru)
wil::com_ptr<T> Pustaka Implementasi Windows Kode C++ COM apa pun; menambahkan pengelogan dan diagnostik
ATL::CComPtr<T> ATL Proyek ATL warisan (hindari untuk kode baru)
// ✅ Smart pointer — Release is called automatically at end of scope
winrt::com_ptr<IStream> stream;
HRESULT hr = CreateStreamOnHGlobal(nullptr, TRUE, stream.put());

// ❌ Raw pointer — easy to leak on early return or exception
IStream* pStream = nullptr;
hr = CreateStreamOnHGlobal(nullptr, TRUE, &pStream);
// ... if any code path forgets pStream->Release(), you have a leak

Aturan manual di bawah ini masih berlaku saat Anda bekerja dengan pointer mentah IUnknown* , kode C warisan, atau menerapkan objek COM secara internal.

Menggunakan jumlah referensi untuk mengelola masa pakai objek memungkinkan beberapa klien untuk mendapatkan dan merilis akses ke satu objek tanpa harus berkoordinasi satu sama lain dalam mengelola masa pakai objek. Selama objek klien sesuai dengan aturan penggunaan tertentu, objek, berlaku, menyediakan manajemen ini. Aturan ini menentukan cara mengelola referensi antar objek. (COM tidak menentukan implementasi internal objek, meskipun aturan ini adalah titik awal yang wajar untuk kebijakan dalam objek.)

Secara konseptual, penunjuk antarmuka dapat dianggap sebagai berada dalam variabel pointer yang mencakup semua status komputasi internal yang menyimpan pointer antarmuka. Ini akan mencakup variabel di lokasi memori, dalam register prosesor internal, dan variabel yang dihasilkan programmer dan dihasilkan kompilator. Penugasan ke atau inisialisasi variabel pointer melibatkan pembuatan salinan baru dari pointer yang sudah ada. Di mana ada satu salinan pointer dalam beberapa variabel (nilai yang digunakan dalam penugasan/inisialisasi), sekarang ada dua. Penugasan ke variabel pointer menghancurkan salinan pointer saat ini dalam variabel, seperti halnya penghancuran variabel itu sendiri. (Artinya, cakupan di mana variabel ditemukan, seperti bingkai tumpukan, dihancurkan.)

Dari perspektif klien COM, penghitungan referensi selalu dilakukan untuk setiap antarmuka. Klien tidak boleh berasumsi bahwa objek menggunakan penghitung yang sama untuk semua antarmuka.

Kasus defaultnya adalah AddRef harus dipanggil untuk setiap salinan baru pointer antarmuka danRilisharus dipanggil untuk setiap penghancuran pointer antarmuka, kecuali jika aturan berikut mengizinkan sebaliknya:

  • Parameter masuk ke fungsi. Pemanggil harus memanggil AddRef pada parameter karena akan dirilis (dengan panggilan ke Rilis) dalam kode penerapan ketika nilai keluar disimpan di atasnya.
  • Mengambil variabel global. Saat membuat salinan lokal penunjuk antarmuka dari salinan pointer yang ada dalam variabel global, Anda harus memanggil AddRef pada salinan lokal karena fungsi lain mungkin menghancurkan salinan dalam variabel global saat salinan lokal masih valid.
  • Pointer baru yang disintesis dari "udara tipis." Fungsi yang mensintesis penunjuk antarmuka menggunakan pengetahuan internal khusus daripada mendapatkannya dari beberapa sumber lain harus memanggil AddRef awalnya pada pointer yang baru disintesis. Contoh penting dari rutinitas tersebut termasuk rutinitas pembuatan instans, implementasi QueryInterface, dan sebagainya.
  • Mengambil salinan pointer yang disimpan secara internal. Saat fungsi mengambil salinan pointer yang disimpan secara internal oleh objek yang disebut, kode objek tersebut harus memanggil AddRef pada pointer sebelum fungsi kembali. Setelah pointer diambil, objek asal tidak memiliki cara lain untuk menentukan bagaimana masa pakainya berkaitan dengan salinan pointer yang disimpan secara internal.

Satu-satunya pengecualian untuk kasus default mengharuskan kode pengelola mengetahui hubungan masa pakai dua atau lebih salinan pointer ke antarmuka yang sama pada objek dan hanya memastikan bahwa objek tidak dihancurkan dengan memungkinkan jumlah referensinya mencapai nol. Umumnya ada dua kasus, sebagai berikut:

  • Ketika satu salinan pointer sudah ada dan yang kedua dibuat kemudian dihancurkan saat salinan pertama masih ada, panggilan ke AddRef dan Release untuk salinan kedua dapat dihilangkan.
  • Ketika satu salinan pointer ada dan satu detik dibuat dan kemudian yang pertama dihancurkan sebelum yang kedua, panggilan ke AddRef untuk salinan kedua dan untuk Rilis untuk salinan pertama dapat dihilangkan.

Berikut ini adalah contoh spesifik dari situasi ini, dua yang pertama sangat umum:

  • Dalam parameter ke fungsi. Masa pakai salinan penunjuk antarmuka yang diteruskan sebagai parameter ke fungsi ditumpuk di penunjuk yang digunakan untuk menginisialisasi nilai, sehingga tidak perlu jumlah referensi terpisah pada parameter .
  • Parameter keluar dari fungsi, termasuk nilai yang dikembalikan. Untuk mengatur parameter, fungsi harus memiliki salinan pointer antarmuka yang stabil. Saat kembali, penelepon bertanggung jawab untuk merilis pointer. Oleh karena itu, parameter keluar tidak memerlukan jumlah referensi terpisah.
  • Variabel lokal. Implementasi metode memiliki kontrol masa pakai masing-masing variabel penunjuk yang dialokasikan pada bingkai tumpukan dan dapat menggunakan ini untuk menentukan cara menghilangkan pasanganRilis AddRef/yang berlebihan.
  • Titik belakang. Beberapa struktur data berisi dua objek, masing-masing dengan penunjuk ke objek lainnya. Jika masa pakai objek pertama diketahui berisi masa pakai yang kedua, tidak perlu memiliki jumlah referensi pada pointer objek kedua ke objek pertama. Seringkali, menghindari siklus ini penting dalam mempertahankan perilaku alokasi yang sesuai. Namun, pointer yang tidak diolah harus digunakan dengan sangat hati-hati karena bagian sistem operasi yang menangani pemrosesan jarak jauh tidak memiliki cara untuk mengetahui tentang hubungan ini. Oleh karena itu, dalam hampir semua kasus, memiliki backpointer melihat objek kedua, "teman" dari pointer pertama (sehingga menghindari melingkar) adalah solusi yang disukai. Arsitektur objek yang dapat dihubungkan COM, misalnya, menggunakan pendekatan ini.

Saat menerapkan atau menggunakan objek yang dihitung referensi, mungkin berguna untuk menerapkan jumlah referensi buatan , yang menjamin stabilitas objek selama pemrosesan fungsi. Dalam menerapkan metode antarmuka, Anda mungkin memanggil fungsi yang berpeluang menurunkan jumlah referensi Anda ke objek, menyebabkan rilis objek prematur dan kegagalan implementasi. Cara yang kuat untuk menghindari hal ini adalah dengan menyisipkan panggilan ke AddRef di awal implementasi metode dan memasangkannya dengan panggilan ke Release tepat sebelum metode kembali.

Dalam beberapa situasi, nilai pengembalianAddRefdan rilis mungkin tidak stabil dan tidak boleh diandalkan; mereka harus digunakan hanya untuk tujuan penelusuran kesalahan atau diagnostik.

Mengelola Masa Pakai Objek Melalui Penghitungan Referensi