Tiga chapter terakhir memberi kita tiga smart pointer, dan sekarang waktunya menyusunnya menjadi satu sistem keputusan yang bisa dipakai tanpa ragu. Kuncinya adalah konsep ownership semantics: setiap pointer — smart maupun mentah — membawa peran kepemilikan yang harus jelas siapa bertanggung jawab melepas objeknya. std::unique_ptr mewakili kepemilikan eksklusif, std::shared_ptr kepemilikan bersama, std::weak_ptr pengamat tanpa kepemilikan, dan pointer mentah berada di posisi paling sederhana: hanya mengamati, tanpa hak dan tanpa tanggung jawab melepas. Chapter ini merangkum keempatnya, mengujinya lewat konvensi passing antar fungsi, dan menutupnya dengan tabel keputusan untuk kode sehari-hari.

Empat Peran Kepemilikan #

AlatPeran kepemilikanBoleh disalinMelepas objek?
std::unique_ptr<T>Eksklusif — satu pemilikTidak (hanya dipindah)Ya, saat pemilik berakhir
std::shared_ptr<T>Bersama — banyak pemilikYa (hitungan naik)Ya, saat hitungan mencapai 0
std::weak_ptr<T>Observer tanpa kepemilikanYa (tidak menambah count)Tidak
Pointer mentah T*Observer tanpa kepemilikanYa (hanya alamat)TIDAK — jangan pernah

Satu baris tabel itu paling sering dilanggar di kode nyata: pointer mentah tetap sah di C++ modern, tetapi rolenya kini tegas — ia melihat, bukan memiliki. Begitu satu alokasi heap punya “pemilik” resmi berupa smart pointer, pointer mentah lain yang menunjuk objek yang sama diperlakukan seperti weak_ptr: hanya membaca.

Konvensi Passing Antar Fungsi #

Semantik kepemilikan paling terasa pada signature fungsi. Tiga konvensi standar: T* atau T& untuk mengamati, std::unique_ptr<T> by value untuk mengambil alih kepemilikan, dan const std::shared_ptr<T>& untuk ikut memakai objek bersama tanpa menyalin. Program berikut menguji ketiganya berdampingan — dan satu catatan: pemindahan kepemilikan di sini memakai std::move, mekanisme yang dibedah di Chapter Pengantar Move Semantics.

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

// T* — hanya mengamati; tidak memiliki, tidak mengubah kepemilikan
void amati(const std::string* teks) {
    if (teks != nullptr) {
        std::cout << "amati : " << *teks << "\n";
    }
}

// unique_ptr by value — kepemilikan PINDAH ke fungsi ini
void konsumsi(std::unique_ptr<std::string> teks) {
    std::cout << "konsum: " << *teks << " (kepemilikan kini di fungsi)\n";
}

// const shared_ptr& — ikut memakai tanpa menyalin; use_count tidak naik
void pakaiBersama(const std::shared_ptr<std::string>& teks) {
    std::cout << "bagi  : " << *teks << " (use_count tetap "
              << teks.use_count() << ")\n";
}

int main() {
    auto rahasia = std::make_unique<std::string>("kode akses 1234");
    amati(rahasia.get());            // observasi lewat pointer mentah
    konsumsi(std::move(rahasia));    // transfer kepemilikan ke fungsi
    std::cout << "rahasia kosong? " << (rahasia == nullptr ? "ya" : "tidak")
              << "\n";

    auto data = std::make_shared<std::string>("profil pengguna");
    pakaiBersama(data);
    std::cout << "use_count akhir: " << data.use_count() << "\n";
    return 0;
}

Output yang Dihasilkan:

text
amati : kode akses 1234
konsum: kode akses 1234 (kepemilikan kini di fungsi)
rahasia kosong? ya
bagi  : profil pengguna (use_count tetap 1)
use_count akhir: 1

Yang dibuktikan oleh ketiga konvensi itu:

  1. Baris 26 (amati(rahasia.get())): fungsi menerima const std::string* — ia bisa membaca, tidak bisa memiliki, dan pointer mentahnya tidak mengubah keadaan rahasia sedikit pun.
  2. Baris 27 (konsumsi(std::move(rahasia))): parameter by value unique_ptr adalah kontrak pengambilalihan; setelah pemanggilan, kepemilikan berpindah — terbukti dari rahasia kosong? ya.
  3. Baris 32 (pakaiBersama(data)): melewatkan const std::shared_ptr& tidak menyalin, sehingga use_count tetap 1 — ikut memakai tanpa ikut memiliki.
  4. Baris 33 (use_count akhir: 1): setelah fungsi kembali, keadaan kepemilikan utuh — data masih satu-satunya pemilik.

Perhatikan kontras baris 2 dan 3: melewatkan unique_ptr by value mengubah siapa pemiliknya, sedangkan const shared_ptr& tidak mengubah apa-apa. Signature fungsi yang tepat membuat maksud kepemilikan terbaca tanpa komentar.

Tabel Keputusan Sehari-hari #

KebutuhanPilihanCatatan
Objek lokal, umur sesuai blokVariabel biasa di stackTidak perlu pointer sama sekali
Satu pemilik, umur melebihi blokstd::unique_ptr<T>Default smart pointer
Banyak pemilik sejajarstd::shared_ptr<T>Sadari biaya blok kontrol
Melihat tanpa mempertahankan hidupstd::weak_ptr<T>Akses lewat lock()
Membaca objek yang dijamin hidupconst T& atau T*Observer, tanpa pelepasan
Array dinamisstd::vector atau std::unique_ptr<T[]>Rujuk Chapter new[] dan delete[]

Sistem kepemilikan ini baru bisa bekerja penuh bila kepemilikan mudah berpindah — dan itulah mekanisme yang belum kita buka: move. Chapter berikutnya mengenalkan move semantics melalui kasus yang sudah kita pakai dua kali di atas: memindahkan unique_ptr tanpa menyalinnya.

Referensi: cppreference — Memory management library , cppreference — std::unique_ptr .