Masalah yang diselesaikan smart pointer
Di halaman sebelumnya kamu mengalokasikan memori dengan new dan membebaskannya dengan delete. Cara itu bekerja, tapi semua bebannya ada padamu: setiap new butuh delete pasangannya, di setiap jalur kode, termasuk jalur saat sebuah exception dilempar di tengah jalan. Kalau terlewat satu, memorimu bocor; kalau delete dijalankan dua kali, heap-nya rusak.
Smart pointer menyelesaikan ini dengan mengikat masa hidup memori heap ke objek biasa di stack. Saat objek itu keluar dari scope, destructor-nya menjalankan delete untukmu, dijamin, bahkan saat exception membongkar stack. Ide ini disebut RAII (Resource Acquisition Is Initialization), dan smart pointer ada di header <memory>.
Kamu memakai *p dan p->member persis seperti pada raw pointer. Bedanya, kamu tidak pernah memanggil delete: smart pointer yang melakukannya.
unique_ptr: satu pemilik, tanpa berbagi
unique_ptr adalah smart pointer yang sebaiknya kamu pakai secara default. Ia mewakili kepemilikan eksklusif: tepat satu unique_ptr memiliki objek itu pada satu waktu, dan saat pointer itu mati, objeknya ikut mati. Dibanding raw pointer, ia tidak punya biaya tambahan saat runtime.
Buat dengan make_unique (C++14). Fungsi ini menerima argumen constructor dan memberimu pointer yang siap dipakai:
Karena hanya boleh ada satu pemilik, unique_ptr tidak bisa disalin. Mencoba menyalinnya menghasilkan error kompilasi, dan error itu adalah cara bahasa ini melindungimu dari dua pemilik yang sama-sama mencoba melakukan delete pada objek yang sama:
auto a = make_unique<int>(10);
auto b = a; // error: call to deleted copy constructor of unique_ptr
Untuk menyerahkan kepemilikan ke pihak lain, kamu memindahkannya dengan std::move. Setelah dipindahkan, pointer aslinya kosong (berisi nullptr):
Inilah model yang paling sering kamu butuhkan: selalu ada tepat satu pemilik yang jelas, dan compiler memastikannya.
shared_ptr: kepemilikan bersama lewat reference count
Kadang beberapa bagian programmu benar-benar perlu berbagi objek yang sama, dan tidak ada yang tahu bagian mana yang akan selesai paling akhir. Untuk itulah shared_ptr ada. Ia menyimpan reference count: setiap salinan menaikkan hitungannya, setiap penghancuran menurunkannya, dan objek baru dibebaskan saat hitungannya mencapai nol.
Buat dengan make_shared:
Berbeda dengan unique_ptr, menyalin shared_ptr tidak masalah, karena memang itulah tujuannya. Gantinya adalah biaya: reference count disimpan di heap dan diperbarui secara atomik (aman untuk thread), jadi shared_ptr lebih berat daripada unique_ptr. Pakai hanya kalau kepemilikannya memang dibagi, bukan sekadar supaya kamu tidak perlu memikirkan siapa pemiliknya.
make_shared juga lebih efisien daripada shared_ptr<T>(new T(...)): ia mengalokasikan objek dan control block dalam satu alokasi, bukan dua.
weak_ptr dan memutus siklus reference
shared_ptr punya satu jebakan klasik: kalau dua objek saling menyimpan shared_ptr ke satu sama lain, reference count mereka tidak pernah mencapai nol, jadi keduanya tidak pernah dibebaskan. Itu memory leak, padahal kamu sudah memakai smart pointer.
struct Node {
shared_ptr<Node> next; // if two nodes point at each other,
}; // they keep each other alive forever
Solusinya adalah weak_ptr: pengamat shared_ptr yang tidak memiliki objeknya. Ia tidak menaikkan reference count, jadi tidak pernah membuat objek tetap hidup. Untuk memakai objeknya, kamu memanggil .lock(), yang memberimu shared_ptr kalau objek itu masih ada, atau yang kosong kalau objeknya sudah hilang.
Pakai weak_ptr untuk "pointer balik" dan cache, yaitu di mana pun kamu ingin merujuk sebuah objek tanpa mengklaim kepemilikannya.
Kesalahan umum dan jebakan
Smart pointer menghilangkan sebagian besar bug memori, tapi masih ada beberapa jebakan:
Jangan mencampur kepemilikan smart dan raw untuk memori yang sama. Jangan pernah membuat dua smart pointer dari raw pointer yang sama, karena masing-masing akan mencoba melakukan delete:
int* raw = new int(5);
unique_ptr<int> a(raw);
unique_ptr<int> b(raw); // disaster: both will delete the same int (double free)
Inilah alasan kamu lebih memilih make_unique/make_shared: tidak ada raw pointer lepas yang bisa disalahgunakan.
unique_ptr hanya bisa dipindahkan, jadi kirim berdasarkan nilai untuk menyerahkan kepemilikan. Kalau sebuah fungsi hanya perlu memakai objek tapi tidak memilikinya, terima reference biasa atau T* mentah. Raw pointer yang hanya mengamati sama sekali tidak masalah:
void consume(unique_ptr<int> p); // takes ownership (move into it)
void observe(int* p); // just looks, owns nothing
Jangan memakai shared_ptr secara default. Memang menggoda karena bisa disalin dengan bebas, tapi reference counting yang atomik punya biaya performa nyata, dan kepemilikan bersama membuat masa hidup objek lebih sulit dipahami. Gunakan unique_ptr sebagai default; beralih ke shared_ptr hanya kalau kamu memang butuh banyak pemilik.
unique_ptr untuk array butuh bentuk array. make_unique<int[]>(n) memberimu unique_ptr<int[]> yang memanggil delete[] dengan benar. Dalam praktiknya, pilih std::vector untuk array dinamis: ia mengelola memorinya untukmu dan juga mencatat ukurannya.
Berikutnya: string
Sekarang manajemen memori sudah kamu kuasai: smart pointer memberimu alokasi heap tanpa memory leak. Salah satu hal paling umum yang akan kamu alokasikan dan kirim ke mana-mana adalah teks, dan C++ memberimu alat yang jauh lebih aman daripada buffer char* mentah. Halaman berikutnya membahas std::string: bagaimana ia bertambah besar sendiri, operasi yang akan kamu pakai setiap hari, dan kenapa ia membebaskanmu sepenuhnya dari urusan memori manual.
Pertanyaan yang sering diajukan
Apa itu smart pointer di C++?
Smart pointer adalah objek dari <memory> (unique_ptr, shared_ptr, weak_ptr) yang membungkus raw pointer dan otomatis melakukan delete pada memorinya saat keluar dari scope. Dengan smart pointer kamu bisa mengalokasikan memori heap tanpa delete manual dan tanpa memory leak yang muncul kalau kamu lupa melakukannya.
Apa perbedaan unique_ptr dan shared_ptr?
unique_ptr adalah satu-satunya pemilik objeknya: ia tidak bisa disalin, hanya bisa dipindahkan, dan langsung membebaskan memori saat ia mati. shared_ptr memungkinkan kepemilikan bersama lewat reference counting: banyak shared_ptr bisa menunjuk ke objek yang sama, dan objek itu baru dibebaskan saat yang terakhir dihancurkan. Pilih unique_ptr kecuali kamu benar-benar butuh kepemilikan bersama.
Sebaiknya pakai make_unique atau new di C++ modern?
Pakai make_unique dan make_shared. Keduanya mengalokasikan dan membungkus objek dalam satu langkah, jadi tidak ada new mentah yang hasilnya bisa bocor sebelum sampai ke smart pointer. Sebagai aturan praktis, codebase C++ modern hampir tidak punya new atau delete polos sama sekali.