Sampai di sini, Section Standard Library telah memberi kita hampir seluruh peralatan penyimpanan data C++: std::string dan string_view, std::array dan std::vector, std::deque dan std::list, std::stack dan std::queue, keluarga std::set dan std::map, pasangan unordered-nya, tipe nilai std::optional/std::variant/std::any, std::pair, std::span, dan std::bitset. Pertanyaannya kini bergeser: bukan lagi “apa itu container”, melainkan “mana yang tepat untuk masalah ini”. Memilih container yang cocok sejak awal membuat kode lebih pendek, lebih cepat, dan lebih sulit salah; memilih yang salah membuat kita menulis ulang logika yang sebenarnya sudah disediakan pustaka. Chapter penutup section ini menyusun kerangka keputusan berupa pertanyaan kunci, tabel ringkas, dan satu program yang memakai satu data mentah untuk empat kebutuhan berbeda.
Pertanyaan yang Menentukan Pilihan #
Empat pertanyaan ini hampir selalu cukup untuk mempersempit pilihan. Pertama, bagaimana kita mengakses datanya — lewat indeks posisi, lewat key bermakna, atau hanya mengambil sesuai urutan tertentu? Kedua, apakah urutan simpan perlu dipertahankan apa adanya (insertion order), dijaga selalu terurut, atau tidak penting sama sekali? Ketiga, bolehkah ada duplikat? Keempat, operasi mana yang paling sering — akses acak, sisip/hapus di tengah, atau pencarian? Jawaban atas empat pertanyaan itu menuntun kita seperti pada diagram berikut:
Alur diagram itu bukan aturan baku, melainkan kebiasaan yang terbukti: std::vector menjadi simpul paling sering dikunjungi karena kebutuhan “deret dengan akses indeks” adalah kasus paling umum, sementara cabang key dan urutan masing-masing bercabang ke keluarga asosiatif dan adapter. Bila satu data lalu diproses dengan cara berbeda-beda, memakai beberapa container sekaligus — bukan memaksakan satu container untuk semua — adalah pilihan yang wajar dan akan kita lihat pada program demo.
Tabel Keputusan Container #
Tabel berikut merangkum seluruh container yang sudah kita pelajari. Kolom “Akses/lookup” memakai notasi kompleksitas — O(1) berarti konstan, O(log n) berarti bertambah pelan sesuai ukuran data, O(n) berarti sebanding dengan ukuran data.
| Container | Pola akses | Urutan | Duplikat | Akses/lookup |
|---|---|---|---|---|
std::array | indeks | sesuai kita | ya | O(1) indeks; ukuran tetap |
std::vector | indeks + ujung belakang | sesuai kita | ya | O(1) indeks; push_back O(1) amortized |
std::deque | indeks + kedua ujung | sesuai kita | ya | O(1) indeks; push/pop kedua ujung O(1) |
std::list | iterator berurutan | sesuai kita | ya | sisip/hapus O(1) di posisi iterator; akses ke-n O(n) |
std::stack | top() saja | LIFO | ya | push/pop/top O(1) |
std::queue | front()/back() saja | FIFO | ya | push/pop O(1) |
std::priority_queue | top() saja | prioritas | ya | push/pop O(log n) |
std::set / std::multiset | pencarian nilai | terurut | tidak / ya | lookup O(log n) |
std::map / std::multimap | pencarian key | terurut | tidak / ya | lookup O(log n) |
std::unordered_set | pencarian nilai | tidak terjamin | tidak | lookup O(1) rata-rata |
std::unordered_map | pencarian key | tidak terjamin | tidak | lookup O(1) rata-rata |
std::flat_map / std::flat_set (C++23) | pencarian key/nilai | terurut | tidak | lookup O(log n) di atas vektor — iterasi ramah cache |
std::span / std::mdspan (non-owning) | indeks + iterator | sesuai sumber | ya | jendela atas data milik lain, tanpa kepemilikan |
optional / variant / any | satu nilai | tidak berlaku | tidak berlaku | utilitas nilai, bukan container |
Baris terakhir menegaskan perbedaan peran: std::optional menjawab “ada atau tidaknya satu nilai”, std::variant “satu nilai dari beberapa tipe”, dan std::any “nilai dengan tipe yang baru diketahui saat runtime” — ketiganya membungkus satu nilai, bukan koleksi. Dua keputusan paling sering salah dikerjakan terletak pada kolom urutan: memilih std::list padahal yang dibutuhkan hanyalah deret (vector lebih cepat berkat kesesuaian dengan cache), dan memilih keluarga unordered padahal hasil perlu ditampilkan terurut (lookup-nya cepat, tetapi urutan iterasinya tidak terjamin).
Satu Data, Empat Kebutuhan #
Program berikut menerima satu log kunjungan mentah yang sama — cukup sebuah std::vector<std::string> — lalu menjawab empat kebutuhan dengan empat container berbeda: frekuensi dengan std::unordered_map, daftar unik terurut dengan std::set, pemrosesan berurutan dengan std::queue, dan peringkat teratas dengan std::priority_queue.
#include <iostream> // std::cout
#include <map> // std::map
#include <queue> // std::queue, std::priority_queue
#include <set> // std::set
#include <string> // std::string, std::to_string
#include <unordered_map> // std::unordered_map
#include <vector> // std::vector
// gabungkan daftar kata menjadi satu baris berpemisah spasi
std::string gabung(const std::vector<std::string>& daftar) {
std::string hasil;
for (std::size_t i = 0; i < daftar.size(); ++i) {
if (i > 0) hasil += " ";
hasil += daftar[i];
}
return hasil;
}
int main() {
// log kunjungan mentah: satu vector sebagai sumber semua skenario
std::vector<std::string> log = {
"beranda", "produk", "beranda", "keranjang",
"produk", "beranda", "produk", "bayar"};
// kebutuhan 1: hitung frekuensi tiap halaman -> unordered_map
std::unordered_map<std::string, int> frekuensi;
for (const std::string& halaman : log) ++frekuensi[halaman];
// cetak lewat std::map agar urutan key deterministik
std::map<std::string, int> terurut(frekuensi.begin(), frekuensi.end());
std::cout << "Frekuensi:\n";
for (const auto& [halaman, jumlah] : terurut) {
std::cout << " " << halaman << " : " << jumlah << "\n";
}
// kebutuhan 2: daftar unik dan terurut -> set
std::set<std::string> unik(log.begin(), log.end());
std::vector<std::string> daftarUnik(unik.begin(), unik.end());
std::cout << "Unik : " << gabung(daftarUnik) << "\n";
// kebutuhan 3: proses sesuai urutan kedatangan -> queue
std::queue<std::string> antrean;
for (const std::string& halaman : log) {
if (halaman == "keranjang" || halaman == "bayar") antrean.push(halaman);
}
std::vector<std::string> urutanProses;
while (!antrean.empty()) {
urutanProses.push_back(antrean.front());
antrean.pop();
}
std::cout << "Antrean : " << gabung(urutanProses) << "\n";
// kebutuhan 4: peringkat halaman terpopuler -> priority_queue
std::priority_queue<std::pair<int, std::string>> peringkat;
for (const auto& [halaman, jumlah] : terurut) {
peringkat.push({jumlah, halaman});
}
std::vector<std::string> teratas;
for (int i = 0; i < 3 && !peringkat.empty(); ++i) {
teratas.push_back(peringkat.top().second
+ " (" + std::to_string(peringkat.top().first) + ")");
peringkat.pop();
}
std::cout << "Terpopuler : " << gabung(teratas) << "\n";
return 0;
}Output yang Dihasilkan:
Frekuensi:
bayar : 1
beranda : 3
keranjang : 1
produk : 3
Unik : bayar beranda keranjang produk
Antrean : keranjang bayar
Terpopuler : produk (3) beranda (3) keranjang (1)Bedah baris demi baris:
- Baris 24-26 (frekuensi):
std::unordered_mapdipilih karena yang dibutuhkan hanyalah pencarian dan penambahan key cepat —++frekuensi[halaman]otomatis membuat entri baru bila key belum ada. - Baris 29-33 (pencetakan):
std::unordered_maptidak menjamin urutan iterasi, sehingga isinya disalin kestd::mapsebelum dicetak. Hasilnya selalubayar,beranda,keranjang,produk— terurut dan deterministik, bukan hasil undian hash. - Baris 36-38 (unik):
std::setdibangun langsung dari rentang log; duplikat hilang sendiri dan urutannya terjaga, tanpa perbandingan manual. - Baris 41-49 (antrean):
std::queuedipakai persis sesuai perannya —pushdi belakang,front()+popdi depan, sehinggakeranjangselalu terproses sebelumbayarsesuai kedatangannya. - Baris 52-61 (peringkat):
std::priority_queuedaristd::pair<int, std::string>selalu meletakkan pasangan terbesar ditop(), sehingga tiga halaman terpopuler terangkat satu per satu tanpa kita mengurutkan apa pun. Jumlah sama diselesaikan lewat perbandingan string pasangan — deterministik, bukan kebetulan. - Fungsi
gabung(baris 10-16): helper kecil untuk mencetak baris dengan pemisah spasi rapi; pemisah diletakkan sebelum tiap elemen kecuali pertama agar tidak ada spasi tersisa di ujung.
Kesalahan Pemilihan yang Sering Terjadi #
Pola-pola berikut muncul berulang kali pada pemula, dan semuanya bisa dihindari dengan kembali ke tabel keputusan:
- Memakai
std::listuntuk semuanya karena takut alokasi ulang vector. Pada ukuran data umum,std::vectorjustru lebih cepat: elemennya berdekatan di memori sehingga ramah cache, sedangkan tiap node list menyendiri di heap. - Memakai
std::unordered_maplalu mencetak isinya mentah dan mengharapkan urutan tertentu. Urutan iterasinya tidak dijanjikan; bila urutan berarti, itu tanda container asosiatif terurut yang dibutuhkan. - Memakai
std::optionalsebagai koleksi — misalnya mencari “nilai mungkin ada” padahal sebenarnya ada banyak kandidat.optionalhanya membungkus satu nilai; koleksi tetap pekerjaan container. - Menyalin data besar hanya untuk melewatkan deret ke fungsi.
std::span(baca/tulis) ataustd::span<const T>(baca-saja) melewatkan jendela tanpa satu elemen pun tersalin.
Dua entri baru pada tabel di atas perlu diberi konteks karena statusnya paling muda. Keluarga std::flat_map dan std::flat_set (C++23) menyimpan elemen di dalam vektor terurut — bukan pohon node seperti std::map — sehingga iterasinya berdekatan di memori dan ramah cache, dengan kompromi penyisipan/penghapusan yang memindahkan elemen. Dukungan compiler-nya baru merata: GCC 15 sudah menyediakannya, sedangkan toolchain lain mengikuti sambil jalan, jadi periksa dulu compiler yang dipakai proyek sebelum memakainya. Adapun std::span yang sudah kita bahas pada chapter sebelumnya menempati posisi berbeda dari semua container di atas: ia tidak memiliki datanya, hanya menampilkan jendela atas milik orang lain — dan C++23 melengkapinya dengan std::mdspan untuk pandangan matriks berdimensi banyak. Keduanya dipilih bukan untuk menyimpan data, melainkan untuk melewatkan data tanpa salinan.
Penutup Section Standard Library #
Dengan kerangka keputusan di tangan, kita menutup Section Standard Library: peralatan penyimpanan dan manipulasi nilai dasar sudah lengkap, mulai dari deret, adapter, keluarga asosiatif, tipe nilai optional/variant/any, hingga span dan bitset. Pola yang sama akan terus kita pakai di seluruh sisa kursus, dan “pilih container sebelum menulis logika” adalah kebiasaan yang sebaiknya terbentuk mulai sekarang.
Langkah berikutnya secara alami adalah memproses isi container-container itu secara idiomatik — bukan lagi dengan loop manual, tetapi dengan iterator dan algoritma pustaka seperti std::sort, std::find, dan std::transform. Itulah persisnya materi Section Iterators dan Algorithms yang menanti di depan.
Rujukan resmi: cppreference — Containers library , cppreference — std::vector , cppreference — std::unordered_map .