Smart pointer (sejak C++11) adalah RAII untuk pointer heap. std::unique_ptr mengelola kepemilikan eksklusif — saat objek keluar scope, destructor otomatis memanggil delete. std::shared_ptr memakai reference counting untuk berbagi kepemilikan; std::weak_ptr adalah referensi non-owning untuk memecah cycle. Untuk DSA, smart pointer menggantikan new/delete manual pada node linked list, tree, dan graph — eliminasi memory leak tanpa overhead signifikan. Pada Chapter ini kita akan membedah tiga jenis smart pointer, membangun linked list dengan unique_ptr, dan menunjukkan jebakan cycle reference.
Tiga Smart Pointer #
| Smart pointer | Kepemilikan | Overhead | Use case |
|---|---|---|---|
std::unique_ptr<T> | Eksklusif (satu pemilik) | Nol (ukuran = pointer) | Default untuk heap; paling efisien |
std::shared_ptr<T> | Berbagi (ref count) | Atomic counter (8 byte) | Bila banyak pemilik butuh akses |
std::weak_ptr<T> | Tidak punya (observer) | Sama dengan shared | Memecah cycle, cache, observer |
Mekanisme: RAII + Ownership #
std::unique_ptr<T> membungkus raw pointer T*. Saat unique_ptr keluar scope, destructor memanggil delete (atau delete[] untuk array) pada pointer. unique_ptr tidak bisa di-copy — hanya di-move. Inilah yang menjamin exactly one pemilik; tidak mungkin ada double-delete.
std::shared_ptr<T> menyimpan dua pointer: ke objek dan ke control block (berisi ref count + weak count + custom deleter). Setiap copy menambah ref count; setiap destructor mengurangi. Bila ref count mencapai 0, objek dihapus; bila weak count juga 0, control block dihapus. Atomic increment membuat operasi thread-safe tetapi lebih lambat dari unique_ptr.
std::weak_ptr<T> menyimpan pointer ke control block tetapi tidak menambah ref count. Untuk mengakses objek, panggil wp.lock() — mengembalikan shared_ptr sementara bila objek masih hidup, atau nullptr shared_ptr bila sudah dihapus.
Kode: Tiga Smart Pointer Berdampingan #
#include <iostream>
#include <memory>
#include <vector>
#include <string>
struct Node {
int val;
std::unique_ptr<Node> next; // kepemilikan eksklusif child
Node(int v) : val(v), next(nullptr) {}
};
// Fungsi yang mengembalikan unique_ptr — kepemilikan dipindah ke pemanggil
std::unique_ptr<Node> make_list(const std::vector<int>& vals) {
std::unique_ptr<Node> head;
for (auto it = vals.rbegin(); it != vals.rend(); ++it) {
auto n = std::make_unique<Node>(*it);
n->next = std::move(head);
head = std::move(n);
}
return head;
}
void print_list(const std::unique_ptr<Node>& head) {
Node* cur = head.get();
while (cur) {
std::cout << cur->val << " -> ";
cur = cur->next.get();
}
std::cout << "nullptr\n";
}
class Graph {
// shared_ptr untuk adjacency — banyak pemilik
struct Adj {
std::vector<std::shared_ptr<int>> weights;
};
public:
std::shared_ptr<int> shared_int = std::make_shared<int>(42);
void demo() {
auto copy1 = shared_int;
auto copy2 = shared_int;
std::cout << "use_count = " << shared_int.use_count() << '\n'; // 3
*copy2 = 99;
std::cout << "*shared_int = " << *shared_int << '\n'; // 99 — shared
}
};
void weak_ptr_demo() {
std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp; // non-owning
std::cout << "sp use_count = " << sp.use_count() << '\n'; // 1
if (auto locked = wp.lock()) {
std::cout << "wp locked: " << *locked << '\n';
}
sp.reset(); // lepaskan kepemilikan
if (wp.expired()) {
std::cout << "wp expired setelah sp.reset()\n";
}
}
// Custom deleter — untuk resource non-new
void custom_deleter_demo() {
auto file_closer = [](FILE* f) {
if (f) { std::fclose(f); std::cout << "file closed\n"; }
};
(void)file_closer; // demo konsep — tidak benar-benar membuka file
std::cout << "(custom deleter tersedia untuk unique_ptr)\n";
}
int main() {
// 1) unique_ptr — eksklusif, otomatis dibebaskan
{
auto p = std::make_unique<int>(42);
std::cout << "unique_ptr: " << *p << " (size=" << sizeof(p) << " bytes)\n";
// auto q = p; // ERROR: unique_ptr tidak bisa di-copy
auto q = std::move(p); // OK: move
std::cout << "after move: p=" << (p ? "non-null" : "null")
<< ", q=" << *q << '\n';
} // q keluar scope → destructor otomatis delete
// 2) unique_ptr untuk array
{
auto arr = std::unique_ptr<int[]>(new int[5]);
for (int i = 0; i < 5; ++i) arr[i] = i * 10;
std::cout << "array: ";
for (int i = 0; i < 5; ++i) std::cout << arr[i] << ' ';
std::cout << '\n';
}
// 3) unique_ptr untuk linked list (DSA!)
{
auto head = make_list({1, 2, 3, 4, 5});
print_list(head);
} // destructor recursive: semua node otomatis dibebaskan
// 4) shared_ptr — berbagi kepemilikan
{
auto sp1 = std::make_shared<std::string>("hello");
auto sp2 = sp1; // copy — reference count naik
std::cout << "shared_ptr use_count = " << sp1.use_count() << '\n';
*sp2 = "world"; // modifikasi terlihat dari sp1
std::cout << "*sp1 = " << *sp1 << '\n';
}
// 5) weak_ptr — non-owning, untuk pecah cycle
weak_ptr_demo();
// 6) shared_ptr demo via class
Graph g;
g.demo();
custom_deleter_demo();
return 0;
}Output yang Dihasilkan:
unique_ptr: 42 (size=8 bytes)
after move: p=null, q=42
array: 0 10 20 30 40
1 -> 2 -> 3 -> 4 -> 5 -> nullptr
shared_ptr use_count = 2
*sp1 = world
sp use_count = 1
wp locked: 10
wp expired setelah sp.reset()
use_count = 3
*shared_int = 99
(custom deleter tersedia untuk unique_ptr)Bedah Kode Baris-per-Baris #
auto p = std::make_unique<int>(42): factory function (C++14) yang membangununique_ptr<int>dengan aman. Mengalokasikan objekintdanunique_ptrsekaligus — lebih efisien daripadaunique_ptr<int>(new int(42))yang melakukan dua alokasi.sizeof(p) == 8:unique_ptrberukuran sama dengan raw pointer (8 byte di 64-bit). Tanpa overhead. Bila pakai custom deleter yang stateful, ukuran bertambah sesuai state deleter.auto q = std::move(p): memindahkan kepemilikan. Setelah move,pmenjadinullptr.p ? "non-null" : "null"menunjukkan status.unique_ptr<int[]>: spesialisasi untuk array — destructor memanggildelete[]. Akses viaarr[i](operator[]).make_unique<int[]>(n)juga tersedia.std::unique_ptr<Node> nextdi structNode: setiap node memilikinextsecara eksklusif. Saat head list keluar scope, destructor recursive membebaskan seluruh node — tidak ada kebocoran.make_list({1,2,3,4,5}): iterasi reverse dari input → prepend setiap node. Pola idiomatik untuk membangun linked list. Setelahhead = std::move(n),nmenjadi null — tidak ada double-free.auto sp2 = sp1: copyshared_ptr— ref count naik dari 1 ke 2. Setelah*sp2 = "world", modifikasi terlihat darisp1karena keduanya menunjuk objek yang sama.use_count() == 2: konfirmasi bahwa dua pemilik aktif. Saat keduasp1dansp2keluar scope, ref count turun ke 0 → objek dihapus.std::weak_ptr<int> wp = sp:wpmengikat ke control blocksptetapi tidak menambah ref count.sp.use_count()tetap1.wp.lock(): mengembalikanshared_ptrsementara — atomic increment + cek apakah objek masih hidup. Bila objek sudah dihapus, mengembalikanshared_ptrkosong (evaluates to false).sp.reset(): decrement ref count → 0 → objek dihapus. Sekarangwp.expired()true.Graph::demo: tigashared_ptrmenunjuk objek yang sama →use_count == 3. Modifikasi via salah satunya terlihat dari semua.
Custom Deleter #
std::unique_ptr<T, Deleter> menerima deleter sebagai parameter template kedua:
auto file = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen("x.txt", "r"), &std::fclose);
auto arr = std::unique_ptr<int, void(*)(int*)>(new int[100],
[](int* p) { std::cout << "custom free\n"; delete[] p; });Berguna untuk resource C-API (FILE*, socket handle, mmap) agar selalu dibebaskan bahkan bila exception.
Jebakan: Cycle Reference #
Dua shared_ptr yang saling menunjuk menyebabkan cycle — ref count tidak pernah mencapai 0:
struct Node {
std::shared_ptr<Node> next; // ❌ bila next->prev menunjuk balik
std::shared_ptr<Node> prev;
};
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b; // b ref count = 2
b->prev = a; // a ref count = 2
// Saat a dan b keluar scope, ref count turun ke 1 (masih saling pegang)
// → MEMORY LEAK
Solusi: salah satu pakai weak_ptr:
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // non-owning — pecah cycle
};Ringkasan #
unique_ptr: kepemilikan eksklusif, tanpa overhead. Default untuk heap DSA.shared_ptr: berbagi kepemilikan via atomic ref count. Lebih mahal — pakai hanya bila butuh.weak_ptr: referensi non-owning, untuk memecah cycle atau observer.make_unique/make_sharedlebih aman dan efisien daripadaneweksplisit.- Custom deleter memungkinkan
unique_ptrmengelola resource non-new (FILE*, socket, mmap). - Hati-hati cycle reference antar
shared_ptr— pecah denganweak_ptr. - Untuk DSA, pakai
unique_ptruntuk child/next/child-tree; pertimbangkanshared_ptr+weak_ptruntuk graph dengan banyak path.