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 pointerKepemilikanOverheadUse 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 sharedMemecah 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 #

cpp
#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:

text
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 #

  1. auto p = std::make_unique<int>(42): factory function (C++14) yang membangun unique_ptr<int> dengan aman. Mengalokasikan objek int dan unique_ptr sekaligus — lebih efisien daripada unique_ptr<int>(new int(42)) yang melakukan dua alokasi.
  2. sizeof(p) == 8: unique_ptr berukuran sama dengan raw pointer (8 byte di 64-bit). Tanpa overhead. Bila pakai custom deleter yang stateful, ukuran bertambah sesuai state deleter.
  3. auto q = std::move(p): memindahkan kepemilikan. Setelah move, p menjadi nullptr. p ? "non-null" : "null" menunjukkan status.
  4. unique_ptr<int[]>: spesialisasi untuk array — destructor memanggil delete[]. Akses via arr[i] (operator[]). make_unique<int[]>(n) juga tersedia.
  5. std::unique_ptr<Node> next di struct Node: setiap node memiliki next secara eksklusif. Saat head list keluar scope, destructor recursive membebaskan seluruh node — tidak ada kebocoran.
  6. make_list({1,2,3,4,5}): iterasi reverse dari input → prepend setiap node. Pola idiomatik untuk membangun linked list. Setelah head = std::move(n), n menjadi null — tidak ada double-free.
  7. auto sp2 = sp1: copy shared_ptr — ref count naik dari 1 ke 2. Setelah *sp2 = "world", modifikasi terlihat dari sp1 karena keduanya menunjuk objek yang sama.
  8. use_count() == 2: konfirmasi bahwa dua pemilik aktif. Saat kedua sp1 dan sp2 keluar scope, ref count turun ke 0 → objek dihapus.
  9. std::weak_ptr<int> wp = sp: wp mengikat ke control block sp tetapi tidak menambah ref count. sp.use_count() tetap 1.
  10. wp.lock(): mengembalikan shared_ptr sementara — atomic increment + cek apakah objek masih hidup. Bila objek sudah dihapus, mengembalikan shared_ptr kosong (evaluates to false).
  11. sp.reset(): decrement ref count → 0 → objek dihapus. Sekarang wp.expired() true.
  12. Graph::demo: tiga shared_ptr menunjuk 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:

cpp
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:

cpp
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:

cpp
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_shared lebih aman dan efisien daripada new eksplisit.
  • Custom deleter memungkinkan unique_ptr mengelola resource non-new (FILE*, socket, mmap).
  • Hati-hati cycle reference antar shared_ptr — pecah dengan weak_ptr.
  • Untuk DSA, pakai unique_ptr untuk child/next/child-tree; pertimbangkan shared_ptr+weak_ptr untuk graph dengan banyak path.