Model dari bagian 1 sudah menetapkan apa yang disimulasikan; sekarang kita memutuskan dengan apa keadaan antrean disimpan. Keputusan struktur data pada proyek nyata jarang sesederhana memilih yang tercepat — ia menentukan seberapa banyak bug yang bisa masuk, seberapa mudah kode dibaca, dan seberapa jauh kita bisa mengembangkan program. Pada bagian ini kita mengulang singkat (recap) std::queue yang pernah dibahas pada chapter queue-dan-deque, melengkapinya dengan struct Teller, lalu membuktikan perilakunya dengan program kecil yang benar-benar kita jalankan.

Operasi yang Dibutuhkan Mesin Simulasi #

Mari kita daftar dulu operasi yang benar-benar dipakai event loop, karena daftar inilah yang menentukan pilihan struktur data:

Operasi pada antreanKapan dipakaiKebutuhan
Tambah pelanggan di belakangsaat kedatangancepat, selalu O(1)
Lihat pelanggan terdepanteller siap melayanitanpa menghapus
Hapus pelanggan terdepanlayanan dimulaicepat, selalu O(1)
Tanya jumlah yang menunggupencatatan statistik per tickmurah
Tanya apakah kosongsyarat teller mengambilmurah

Pola “tambah di satu ujung, ambil dari ujung lain” adalah definisi antrean FIFO, dan std::queue adalah wadah standar yang dikhususkan untuk pola ini. Dengan menyediakan hanya lima operasi di atas — tanpa akses acak, tanpa iterasi bebas — adaptor ini mencegah kita diam-diam “memotret” antrean di tengah, perilaku yang akan merusak makna model.

std::queue: Adaptor FIFO dari Standard Library #

std::queue bukan container berdiri sendiri, melainkan container adaptor: pembungkus tipis di atas container lain (secara bawaan std::deque) yang membatasi antarmuka menjadi FIFO saja. API yang kita pakai hanya enam function:

FunctionPerilaku
push(x)menyisipkan x di belakang antrean
pop()menghapus elemen terdepan, tanpa mengembalikannya
front()mengembalikan referensi elemen terdepan
back()mengembalikan referensi elemen terakhir
size()banyaknya elemen
empty()true bila antrean kosong

Dua struct pelengkap model kita juga jadi di bagian ini: Teller yang mencatat kapan loket kembali bebas, dan antrean itu sendiri di dalam mesin simulasi:

cpp src/simulasi-antrean.cpp — struct Teller dan antrean dalam jalankanSimulasi
struct Teller {
    int id;
    int bebasPada;      // menit teller bebas melayani lagi
    int jumlahDilayani; // banyak pelanggan yang sudah dilayani
};

// Di dalam jalankanSimulasi():
std::queue<Pelanggan> antrean;
Teller teller{1, 0, 0};
  1. bebasPada: satu field yang merangkum seluruh keadaan teller — bila tick >= bebasPada, loket siap menerima. Tidak perlu flag sedangSibuk terpisah karena waktu bebas sudah mengandung informasi itu.
  2. jumlahDilayani: penghitung untuk perhitungan utilisasi di bagian 4; dilacak saat kejadian terjadi, bukan dihitung ulang dari riwayat di akhir.
  3. std::queue<Pelanggan> antrean: elemennya disalin (by value) ke dalam antrean; Pelanggan berukuran kecil (empat int), sehingga salinan murah dan kita bebas dari kepusingan lifetime.

Mencoba Perilaku FIFO Secara Nyata #

Sebelum menyelimuti antrean dengan logika simulasi, kita buktikan dulu perilaku dasarnya dengan program kecil. Program ini mengantre tiga pelanggan bernama, memeriksa ujung antrean, lalu memanggil mereka satu per satu:

cpp demo-antrean.cpp — program lengkap untuk bagian ini
// demo-antrean.cpp — Bagian 2: std::queue sebagai antrean teller.
// Membuktikan perilaku FIFO: pelanggan yang datang lebih dulu dilayani lebih dulu.
#include <iostream>
#include <queue>
#include <string>

struct Pelanggan {
    int id;
    std::string nama;
};

int main() {
    std::queue<Pelanggan> antrean;

    // Tiga pelanggan datang; yang pertama masuk paling depan.
    antrean.push(Pelanggan{1, "Budi"});
    antrean.push(Pelanggan{2, "Siti"});
    antrean.push(Pelanggan{3, "Dewi"});

    std::cout << "Isi antrean: " << antrean.size() << " pelanggan\n";
    std::cout << "Depan   : p" << antrean.front().id << " ("
              << antrean.front().nama << ")\n";
    std::cout << "Belakang: p" << antrean.back().id << " ("
              << antrean.back().nama << ")\n";

    // Teller memanggil satu per satu dari depan.
    while (!antrean.empty()) {
        Pelanggan p = antrean.front();
        antrean.pop(); // pop() menghapus, tetapi TIDAK mengembalikan nilai
        std::cout << "Teller memanggil p" << p.id << " (" << p.nama
                  << "), sisa antrean: " << antrean.size() << '\n';
    }
    std::cout << "Antrean kosong, teller istirahat.\n";
    return 0;
}
bash
g++ -std=c++23 -Wall -Wextra demo-antrean.cpp -o demo-antrean
./demo-antrean

Output yang Dihasilkan:

text
Isi antrean: 3 pelanggan
Depan   : p1 (Budi)
Belakang: p3 (Dewi)
Teller memanggil p1 (Budi), sisa antrean: 2
Teller memanggil p2 (Siti), sisa antrean: 1
Teller memanggil p3 (Dewi), sisa antrean: 0
Antrean kosong, teller istirahat.

Perhatikan tiga hal dari output di atas. Pertama, urutan pemanggilan persis urutan kedatangan — inilah jaminan FIFO yang nantinya kita kunci sebagai assertion di harness. Kedua, loop while (!antrean.empty()) memakai pola aman front() lalu pop(); variabel p memegang salinan sehingga mencetak setelah pop() tetap sah. Ketiga, size() menurun dengan tertib dari 2 ke 0 — kalau suatu saat angka ini aneh, bug-nya ada di pola ambil-hapus kita, bukan di library.

std::queue atau Linked List Buatan Sendiri #

Chapter linked-list mengajarkan cara membangun antrean dari simpul-simpul yang dihubungkan pointer. Kapan kita memilih yang buatan sendiri?

Aspekstd::queueLinked list buatan sendiri
Kebenaranteruji dan dijaga standarseluruh risiko pointer ada pada kita
Bug khashampir tidak adamemory leak, dangling pointer, lupa sisi belakang
Keterbacaantinggi, maksud langsung terlihatperlu komentar dan disiplin
Kontrol memoridiserahkan ke adaptorbebas: pool simpul, alokasi khusus
Peran belajarpemakaianmemahami cara kerja di balik layar

Untuk proyek ini kita memilih std::queue, dan pilihan itu adalah keputusan rekayasa, bukan kelembekan: tujuan simulator adalah statistik yang benar, bukan demonstrasi manajemen pointer. Linked list buatan sendiri tetap layak ketika kita butuh kontrol khusus — misalnya menghindari alokasi per-kedatangan pada simulasi jutaan entitas — dan bab memilih-struktur-data membahas trade-off tersebut lebih dalam.

Dengan struct Pelanggan, struct Teller, dan antrean yang perilakunya sudah terbukti, semua peran sudah terisi. Bagian berikutnya menyusunnya menjadi mesin simulasi yang benar-benar berjalan menit demi menit.