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.
#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:
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:
- Baris 19 (
std::make_unique<int>(42)): alokasi heap dan pembungkusan terjadi dalam satu ekspresi;automenyembunyikan tipe panjangstd::unique_ptr<int>. - Baris 20 dan 23 (
*angkadan->): operator*dan->meneruskan akses ke pointer di dalamnya, sehingga kode pemakai terasa seperti pointer biasa. - Baris 25 (
profil.get()): mengeluarkan pointer mentah untuk keperluan observasi atau integrasi dengan kode lama — tanpa mengalihkan kepemilikan, dan tanpa boleh di-delete. - Baris 28-29 (dua
sizeof): keduanya bernilai 8 byte pada mesin 64-bit —unique_ptrhanya membungkus satu pointer, itulah makna zero overhead yang sering disebut. - Baris 31 (
profil.reset();): destructorProfilberjalan tepat di baris ini, terbukti dari urutan output; tanpareset()pun ia akan berjalan di akhirmain().
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:
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 #
*pdanp->: mengakses objek yang dimiliki — pemakaian sehari-hari.p.get(): mengambil pointer mentah untuk dilempar ke fungsi yang hanya mengamati; kepemilikan tetap dip.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 .