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.

ContainerPola aksesUrutanDuplikatAkses/lookup
std::arrayindekssesuai kitayaO(1) indeks; ukuran tetap
std::vectorindeks + ujung belakangsesuai kitayaO(1) indeks; push_back O(1) amortized
std::dequeindeks + kedua ujungsesuai kitayaO(1) indeks; push/pop kedua ujung O(1)
std::listiterator berurutansesuai kitayasisip/hapus O(1) di posisi iterator; akses ke-n O(n)
std::stacktop() sajaLIFOyapush/pop/top O(1)
std::queuefront()/back() sajaFIFOyapush/pop O(1)
std::priority_queuetop() sajaprioritasyapush/pop O(log n)
std::set / std::multisetpencarian nilaiteruruttidak / yalookup O(log n)
std::map / std::multimappencarian keyteruruttidak / yalookup O(log n)
std::unordered_setpencarian nilaitidak terjamintidaklookup O(1) rata-rata
std::unordered_mappencarian keytidak terjamintidaklookup O(1) rata-rata
std::flat_map / std::flat_set (C++23)pencarian key/nilaiteruruttidaklookup O(log n) di atas vektor — iterasi ramah cache
std::span / std::mdspan (non-owning)indeks + iteratorsesuai sumberyajendela atas data milik lain, tanpa kepemilikan
optional / variant / anysatu nilaitidak berlakutidak berlakuutilitas 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.

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

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

  1. Baris 24-26 (frekuensi): std::unordered_map dipilih karena yang dibutuhkan hanyalah pencarian dan penambahan key cepat — ++frekuensi[halaman] otomatis membuat entri baru bila key belum ada.
  2. Baris 29-33 (pencetakan): std::unordered_map tidak menjamin urutan iterasi, sehingga isinya disalin ke std::map sebelum dicetak. Hasilnya selalu bayar, beranda, keranjang, produk — terurut dan deterministik, bukan hasil undian hash.
  3. Baris 36-38 (unik): std::set dibangun langsung dari rentang log; duplikat hilang sendiri dan urutannya terjaga, tanpa perbandingan manual.
  4. Baris 41-49 (antrean): std::queue dipakai persis sesuai perannya — push di belakang, front() + pop di depan, sehingga keranjang selalu terproses sebelum bayar sesuai kedatangannya.
  5. Baris 52-61 (peringkat): std::priority_queue dari std::pair<int, std::string> selalu meletakkan pasangan terbesar di top(), sehingga tiga halaman terpopuler terangkat satu per satu tanpa kita mengurutkan apa pun. Jumlah sama diselesaikan lewat perbandingan string pasangan — deterministik, bukan kebetulan.
  6. 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::list untuk semuanya karena takut alokasi ulang vector. Pada ukuran data umum, std::vector justru lebih cepat: elemennya berdekatan di memori sehingga ramah cache, sedangkan tiap node list menyendiri di heap.
  • Memakai std::unordered_map lalu mencetak isinya mentah dan mengharapkan urutan tertentu. Urutan iterasinya tidak dijanjikan; bila urutan berarti, itu tanda container asosiatif terurut yang dibutuhkan.
  • Memakai std::optional sebagai koleksi — misalnya mencari “nilai mungkin ada” padahal sebenarnya ada banyak kandidat. optional hanya membungkus satu nilai; koleksi tetap pekerjaan container.
  • Menyalin data besar hanya untuk melewatkan deret ke fungsi. std::span (baca/tulis) atau std::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 .