std::shared_ptr menyelesaikan kepemilikan bersama, tetapi mekanisme hitungnya membawa lubang kecil yang klasik: bila dua objek saling memegang shared_ptr satu sama lain, hitungan mereka tidak akan pernah mencapai nol — keduanya menunggu yang lain dilepas lebih dulu, dan keduanya hidup sampai program berakhir. Kebocoran ala reference counting ini disebut circular reference. Solusi pustaka standar adalah std::weak_ptr<T>: pointer yang mengamati objek milik shared_ptr tanpa ikut menghitung sebagai pemilik. Chapter ini memvisualisasikan simpul kebocorannya, lalu membuktikan bahwa mengganti satu sisi panah dengan weak_ptr memulihkan pelepasan objek secara utuh.
Circular Reference: Hitungan yang Tak Pernah Nol #
Bayangkan dua objek yang saling merujuk — misalnya node list berantai yang menunjuk node berikutnya sekaligus sebelumnya. Bila KEDUA arah dipakai shared_ptr, masing-masing objek menganggap dirinya masih dipakai oleh yang lain:
Pada pola kiri, use_count A minimal 1 karena dipegang B, dan use_count B minimal 1 karena dipegang A — pelepasan dari luar pun tak pernah cukup untuk mencapai nol. Pada pola kanan, panah mundur diberi weak_ptr: ia mengamati A tanpa menambah hitungan, sehingga hitungan A kembali bergantung pada pemilik sebenarnya saja. Dalam kode, versi bermasalahnya tampak seperti ini:
struct Anak;
struct Ayah {
std::shared_ptr<Anak> anak;
};
struct Anak {
std::shared_ptr<Ayah> ayah; // harusnya std::weak_ptr<Ayah>
};
// a->ayah = b; b->anak = a; -> kedua use_count tak pernah 0
weak_ptr: Mengamati Tanpa Memiliki #
std::weak_ptr tidak bisa mendereferensikan objeknya secara langsung — memang seharusnya, karena objek itu bisa berakhir kapan saja tanpa izinnya. Aksesnya lewat lock(), yang mengembalikan shared_ptr sementara: berhasil bila objek masih hidup, kosong bila sudah dilepas. Untuk pemeriksaan murah, expired() melaporkan apakah objek sudah berakhir. Program berikut merakit dua simpul yang saling merujuk dengan pola yang aman — panah maju shared_ptr, panah mundur weak_ptr — lalu membongkarnya:
#include <iostream>
#include <memory>
#include <string>
class Simpul {
public:
explicit Simpul(const std::string& nama) : nama_(nama) {
std::cout << nama_ << " diciptakan\n";
}
~Simpul() {
std::cout << nama_ << " dibebaskan\n";
}
std::shared_ptr<Simpul> berikutnya; // panah maju: memiliki
std::weak_ptr<Simpul> sebelumnya; // panah mundur: hanya mengamati
const std::string& nama() const { return nama_; }
private:
std::string nama_;
};
int main() {
auto a = std::make_shared<Simpul>("A");
auto b = std::make_shared<Simpul>("B");
a->berikutnya = b; // A memegang B (shared_ptr)
b->sebelumnya = a; // B mengamati A (weak_ptr — tidak menambah count)
std::cout << "a.use_count = " << a.use_count() << "\n";
std::cout << "b.use_count = " << b.use_count() << "\n";
// Akses aman lewat lock(): menghasilkan shared_ptr sementara
if (std::shared_ptr<Simpul> aman = b->sebelumnya.lock()) {
std::cout << "lock() berhasil, mengamati: " << aman->nama() << "\n";
}
a.reset(); // A dibebaskan karena count-nya tidak dipegang weak_ptr
std::cout << "b->sebelumnya expired? "
<< (b->sebelumnya.expired() ? "ya" : "tidak") << "\n";
b.reset(); // B dibebaskan
std::cout << "selesai\n";
return 0;
}Output yang Dihasilkan:
A diciptakan
B diciptakan
a.use_count = 1
b.use_count = 2
lock() berhasil, mengamati: A
A dibebaskan
b->sebelumnya expired? ya
B dibebaskan
selesaiBagian-bagian pembuktian dari program di atas:
- Baris 26-27 (pengaitan dua arah):
berikutnyabertipeshared_ptrmenambah hitungan B, sedangkansebelumnyabertipeweak_ptrtidak menambah hitungan A — terbukti daria.use_count = 1. - Baris 33 (
lock()):b->sebelumnya.lock()menghasilkanshared_ptrsementara bernamaaman; hitungan A naik selamaamanhidup, dan A dijamin tidak bisa berakhir di tengah pemakaiannya. - Baris 37 (
a.reset()): karenaweak_ptrtidak ikut menghitung, hitungan A menyentuh nol danA dibebaskantercetak — inilah yang takkan pernah terjadi pada pola circular. - Baris 38-39 (
expired()):weak_ptryang tersisa jujur melaporkan kondisi objeknya:expired? ya. Pembandingif (auto s = w.lock())menjadi cara idiomatik membaca keadaan ini. - Baris 41 (
b.reset()): panah milik A sudah ikut terlepas bersama A, sehingga hitungan B kini tinggal satu — dan B pun dilepas.
Kapan weak_ptr Layak Dipakai #
- Back-reference pada struktur berantai: anak menunjuk induk, mundur menunjuk maju — arah yang tidak harus mempertahankan hidup target.
- Cache: cache menyimpan
weak_ptragar objek tidak dipertahankan hidup hanya karena tercatat; bila pemilik sebenarnya sudah pergi, cache laporkan kosong. - Observer: banyak pengamat melihat satu subjek tanpa mencegah subjek berakhir; terlihat pada Section Concurrency dan Parallelism nanti dalam bentuk serupa.
Dengan weak_ptr, tiga smart pointer pustaka standar sudah lengkap: pemilik tunggal, pemilik bersama, dan pengamat. Chapter berikutnya menyusun ketiganya — ditambah pointer mentah — menjadi satu peta kepemilikan yang menentukan alat mana untuk kebutuhan mana.
Referensi: cppreference — std::weak_ptr , cppreference — weak_ptr::lock , cppreference — weak_ptr::expired .