std::unique_ptr menjawab sebagian besar kebutuhan dengan satu pemilik, tetapi ada situasi yang memang tidak bisa direduksi menjadi satu: objek yang dipakai bersama beberapa bagian program tanpa ada satu pemegang utama. Untuk kasus itu pustaka standar menyediakan std::shared_ptr<T> — smart pointer dengan kepemilikan bersama berbasis reference counting. Setiap salinan shared_ptr menambah satu penghitung pemilik; setiap shared_ptr yang berakhir menguranginya; dan begitu hitungan mencapai nol, objek akhirnya dilepas. Chapter ini membedah mekanisme hitung itu lewat program yang bisa diuji, lalu menimbang biayanya supaya kita tahu kapan shared_ptr tepat dan kapan ia kebablasan.

Mekanisme Reference Counting #

Di balik setiap objek yang dibagi, shared_ptr menjaga satu blok kontrol — struktur pembukuan yang memuat penghitung jumlah pemilik (use count) plus data lain yang menyusul di chapter berikutnya. Saat shared_ptr disalin, hitungan naik; saat salah satu salinan keluar dari scope, hitungan turun; saat hitungan menyentuh nol, destructor objek dijalankan dan blok kontrol ikut dilepas. Yang penting dicatat: objek yang dibagi tetap hanya satu — salinan shared_ptr menyalin pembukuan, bukan objeknya. Semua itu berjalan otomatis, dan kita bisa mengamati hitungannya kapan pun lewat use_count().

cpp
#include <iostream>
#include <memory>
#include <string>

class Data {
public:
    explicit Data(const std::string& label) : label_(label) {
        std::cout << label_ << " diciptakan\n";
    }
    ~Data() {
        std::cout << label_ << " dibebaskan\n";
    }
    const std::string& label() const { return label_; }
private:
    std::string label_;
};

int main() {
    auto a = std::make_shared<Data>("sensor-suhu");
    std::cout << "use_count awal        : " << a.use_count() << "\n";

    {
        auto b = a;   // salinan: dua pemilik untuk satu objek
        std::cout << "use_count dalam blok  : " << a.use_count() << "\n";
        std::cout << "b membaca             : " << b->label() << "\n";
    }   // b berakhir: jumlah pemilik turun

    std::cout << "use_count setelah blok: " << a.use_count() << "\n";
    return 0;   // a berakhir: count mencapai 0, objek dibebaskan
}

Output yang Dihasilkan:

text
sensor-suhu diciptakan
use_count awal        : 1
use_count dalam blok  : 2
b membaca             : sensor-suhu
use_count setelah blok: 1
sensor-suhu dibebaskan

Bacaan urut dari output di atas:

  1. Baris 19 (std::make_shared<Data>(...)): satu objek Data dan blok kontrolnya dialokasikan; hitungan awalnya 1 karena hanya a yang memiliki.
  2. Baris 23 (auto b = a;): penyalinan SAH untuk shared_ptr — berbeda dari unique_ptr. Hitungan naik menjadi 2, dan b menunjuk objek yang sama, bukan salinan datanya.
  3. Baris 25 (b->label()): kedua pemilik memakai objek yang sama dengan hak akses yang setara.
  4. Baris 26 (}): b berakhir bersama bloknya; hitungan turun ke 1. Objek belum dilepas karena masih ada pemilik.
  5. Baris 29 (return 0;): a berakhir, hitungan menyentuh 0, dan barulah pesan dibebaskan tercetak.

Biaya yang Kita Bayar #

Mekanisme hitung ini bukan gratis, dan menimbang biayanya membantu kita memilih dengan sadar:

  • Memori tambahan: shared_ptr berukuran dua pointer — satu ke objek, satu ke blok kontrol — dan blok kontrol itu sendiri menempati memori terpisah.
  • Alokasi ekstra: make_shared memang menyatukan objek dan blok kontrol dalam satu alokasi, tetapi tetap ada lebih banyak data yang dikelola dibanding unique_ptr.
  • Sinkronisasi: penambahan dan pengurangan hitungan harus aman di lingkungan multithread, sehingga operasi penyalinan membawa biaya atomik.

Itulah alasan aturan praktisnya berbunyi: unique_ptr adalah default, shared_ptr hanya bila kepemilikan memang dibagi secara nyata. Tanda “dibagi nyata” biasanya terlihat dari desain — beberapa bagian program perlu menyimpan rujukan ke objek yang sama tanpa ada yang menjadi pemegang utama, misalnya resource yang di-cache atau konfigurasi yang dipakai lintas modul.

Ada satu kelemahan halus yang menanti di balik kepemilikan bersama: bila dua shared_ptr saling menunjuk, hitungan mereka tidak pernah mencapai nol. Chapter berikutnya membahas std::weak_ptr — observer tanpa kepemilikan yang dirancang khusus untuk membongkar simpul itu.

Referensi: cppreference — std::shared_ptr , cppreference — std::make_shared , cppreference — use_count .