Semua fondasi sudah terkumpul, dan sekarang kita sampai pada alat yang menjadi default kode C++ modern: std::unique_ptr<T>. Kelas template dari header <memory> ini membungkus pointer mentah dan melepas objek yang ditunjuknya lewat destructor — RAII siap pakai untuk kepemilikan memori. Sifat utamanya tertulis di namanya: unique, artinya kepemilikan itu tunggal. Pada satu waktu hanya ada satu unique_ptr yang memiliki objek tersebut, dan penyalinannya dilarang oleh compiler. Saat unique_ptr berakhir, objek heap di dalamnya ikut berakhir — tepat seperti kelas RAII yang kita bangun sendiri di chapter sebelumnya, tetapi tanpa harus menulis kelasnya.

make_unique dan Pemakaian Dasar #

Cara menciptakannya juga sudah dibakukan sejak C++14: std::make_unique<T>(...) mengalokasikan objek di heap dan membungkusnya dalam unique_ptr dalam satu ekspresi. Pemakaiannya nyaris tidak bisa dibedakan dari pointer biasa — operator * mendereferensikan, operator -> mengakses anggota, dan objek yang ditunjuk tetap hidup di heap.

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

class Profil {
public:
    explicit Profil(const std::string& nama) : nama_(nama) {
        std::cout << "Profil " << nama_ << " diciptakan\n";
    }
    ~Profil() {
        std::cout << "Profil " << nama_ << " dibebaskan otomatis\n";
    }
    const std::string& nama() const { return nama_; }
private:
    std::string nama_;
};

int main() {
    auto angka = std::make_unique<int>(42);
    std::cout << "Nilai: " << *angka << "\n";            // operator*

    auto profil = std::make_unique<Profil>("Budi");
    std::cout << "Nama  : " << profil->nama() << "\n";   // operator->

    Profil* mentah = profil.get();   // observasi saja, jangan di-delete
    std::cout << "get() : " << mentah->nama() << "\n";

    std::cout << "sizeof unique_ptr : " << sizeof(profil) << "\n";
    std::cout << "sizeof raw pointer: " << sizeof(Profil*) << "\n";

    profil.reset();   // destructor Profil berjalan pada baris ini
    std::cout << "Setelah reset()\n";
    return 0;
}

Output yang Dihasilkan:

text
Nilai: 42
Profil Budi diciptakan
Nama  : Budi
get() : Budi
sizeof unique_ptr : 8
sizeof raw pointer: 8
Profil Budi dibebaskan otomatis
Setelah reset()

Bagian-bagian penting dari program di atas:

  1. Baris 19 (std::make_unique<int>(42)): alokasi heap dan pembungkusan terjadi dalam satu ekspresi; auto menyembunyikan tipe panjang std::unique_ptr<int>.
  2. Baris 20 dan 23 (*angka dan ->): operator * dan -> meneruskan akses ke pointer di dalamnya, sehingga kode pemakai terasa seperti pointer biasa.
  3. Baris 25 (profil.get()): mengeluarkan pointer mentah untuk keperluan observasi atau integrasi dengan kode lama — tanpa mengalihkan kepemilikan, dan tanpa boleh di-delete.
  4. Baris 28-29 (dua sizeof): keduanya bernilai 8 byte pada mesin 64-bit — unique_ptr hanya membungkus satu pointer, itulah makna zero overhead yang sering disebut.
  5. Baris 31 (profil.reset();): destructor Profil berjalan tepat di baris ini, terbukti dari urutan output; tanpa reset() pun ia akan berjalan di akhir main().

Tidak Bisa Disalin — dan Itu Fiturnya #

Kepemilikan tunggal dijaga dengan mekanisme bahasa: copy constructor dan copy assignment unique_ptr sengaja dideklarasikan sebagai delete. Bila kita memaksa menyalinnya, compiler menolak dengan pesan yang eksplisit:

text Error g++ 14.2 saat menyalin unique_ptr
error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
    5 |     auto b = a;   // mencoba menyalin unique_ptr
      |              ^
/usr/include/c++/14/bits/unique_ptr.h:517:7: note: declared here
  517 |       unique_ptr(const unique_ptr&) = delete;

Larangan ini menutup sumber bug klasik RAII manual yang kita singgung di chapter sebelumnya: dua “pemilik” yang sama-sama melepas memori yang sama. Kepemilikan tetap bisa berpindah — tetapi hanya lewat mekanisme pemindahan (move) yang menuntut eksplisit, topik yang kita singgung di Chapter Pengantar Move Semantics. Akibat praktisnya: fungsi yang menerima unique_ptr by value berarti mengambil alih kepemilikan, dan itu terbaca jelas dari signature-nya.

Anggota yang Perlu Dikuasai #

  • *p dan p->: mengakses objek yang dimiliki — pemakaian sehari-hari.
  • p.get(): mengambil pointer mentah untuk dilempar ke fungsi yang hanya mengamati; kepemilikan tetap di p.
  • p.reset(): melepas objek sekarang (atau menggantinya); berguna bila kita ingin pelepasan lebih cepat dari akhir scope.
  • p.release(): menyerahkan kepemilikan keluar sebagai pointer mentah — pemanggil yang menerima jadi bertanggung jawab melepaskannya; pemakaiannya harus dibenarkan, bukan dibiasakan.

std::unique_ptr menyelesaikan hampir semua kebutuhan kepemilikan, dan karena itu ia menjadi default. Namun ada skenario yang memang butuh lebih dari satu pemilik — misalnya data yang dipakai bersama beberapa modul tanpa urutan kepemilikan yang jelas. Chapter berikutnya membahas std::shared_ptr, beserta biaya mekanisme hitung pemiliknya.

Referensi: cppreference — std::unique_ptr , cppreference — std::make_unique .