Esensi std::vector

Array dinamis, push_back, dan perbedaan size terhadap capacity.

Daftar isi

Topik ini membahas std::vector, container atau wadah pustaka standar yang paling sering dipakai untuk kumpulan elemen yang jumlahnya berubah saat program berjalan. Pembahasannya mencakup penambahan elemen dengan push_back, perbedaan antara size() dan capacity(), serta fungsi reserve() untuk menyiapkan ruang lebih dahulu. Setelah membaca topik ini, cara kerja alokasi ulang memori dan kebiasaan penggunaan std::vector yang efisien sudah dapat dijelaskan.

Deklarasi dan penambahan elemen#

std::vector berasal dari header <vector> dan menyimpan elemen bertipe sama dalam memori yang berurutan, seperti array, tetapi ukurannya dapat bertambah dan berkurang. Vector kosong dideklarasikan tanpa isi, kemudian diisi satu elemen demi satu elemen dengan push_back(), yang menambahkan elemen baru tepat setelah elemen terakhir. Ukuran elemen saat ini dibaca dengan size(), sedangkan pop_back() menghapus elemen terakhirnya.

cpp daftar-belanja.cpp
#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> belanja;
    belanja.push_back("beras");
    belanja.push_back("telur");
    belanja.push_back("minyak");
    belanja.push_back("gula");

    std::cout << "Isi daftar (" << belanja.size() << " butir):\n";
    for (const std::string& b : belanja) {
        std::cout << "- " << b << '\n';
    }

    belanja.pop_back();
    std::cout << "Setelah pop_back: " << belanja.size() << " butir\n";
    std::cout << "Butir pertama: " << belanja.front() << '\n';
    std::cout << "Butir terakhir: " << belanja.back() << '\n';
    return 0;
}
text Keluaran
Isi daftar (4 butir):
- beras
- telur
- minyak
- gula
Setelah pop_back: 3 butir
Butir pertama: beras
Butir terakhir: minyak

Apabila isinya sudah diketahui sejak awal, vector juga dapat dideklarasikan dengan daftar inisialisasi, misalnya std::vector<int> nilai = {70, 85, 90};, tanpa push_back() sama sekali. Member function front() dan back() membaca elemen pertama dan terakhir tanpa menyalin vectornya, sedangkan akses per indeks memakai belanja[i] seperti array biasa. Kombinasi inilah yang menjadikan vector pilihan awal untuk kumpulan data pada hampir semua program C++ modern.

Vector juga dapat dibuat dengan ukuran awal tertentu, misalnya std::vector<int> penghitung(5); yang berisi lima elemen bernilai 0, atau dengan pasangan ukuran dan nilai awal seperti std::vector<int> penghitung(5, 1); yang berisi lima elemen bernilai 1. Bentuk ini berguna ketika banyaknya kotak sudah diketahui, misalnya penghitung untuk lima kategori nilai. Setelah terbentuk, elemennya tetap dapat ditambah dengan push_back() seperti biasa.

size dan capacity#

size() menyatakan banyaknya elemen yang benar-benar tersimpan, sedangkan capacity() menyatakan jumlah memori yang sudah dialokasikan untuk vector tersebut. Ketika ruang yang dialokasikan habis dan push_back() tetap dipanggil, vector melakukan realokasi, yaitu menyediakan blok memori yang lebih besar, memindahkan seluruh elemen ke sana, lalu melepaskan blok lama. Realokasi inilah yang membuat capacity() biasanya lebih besar daripada size().

cpp size-kapasitas.cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> angka;
    for (int i = 1; i <= 10; ++i) {
        angka.push_back(i);
        std::cout << "push_back ke-" << i
                  << ": size = " << angka.size()
                  << ", capacity = " << angka.capacity() << '\n';
    }
    return 0;
}
text Keluaran
push_back ke-1: size = 1, capacity = 1
push_back ke-2: size = 2, capacity = 2
push_back ke-3: size = 3, capacity = 4
push_back ke-4: size = 4, capacity = 4
push_back ke-5: size = 5, capacity = 8
push_back ke-6: size = 6, capacity = 8
push_back ke-7: size = 7, capacity = 8
push_back ke-8: size = 8, capacity = 8
push_back ke-9: size = 9, capacity = 16
push_back ke-10: size = 10, capacity = 16

Keluaran tersebut memperlihatkan lonjakan capacity pada elemen ke-3, ke-5, dan ke-9, yaitu tepat ketika ruang sebelumnya habis. Nilai size tetap mengikuti banyaknya elemen yang dimasukkan, sedangkan capacity bergerak dalam langkah besar agar push_back() berikutnya tidak langsung memicu realokasi. Dengan demikian, vector menukar sedikit memori berlebih dengan lebih sedikit pemindahan elemen.

Realokasi juga memiliki konsekuensi yang melampaui biaya pemindahan. Referensi, pointer, dan iterator yang menunjuk ke elemen vector menjadi tidak sah setelah realokasi, karena seluruh elemen pindah ke blok memori baru. Karena itu, perulangan yang sedang memegang iterator atau referensi ke elemen vector tidak boleh menambah elemen selama berlangsungnya, sebagaimana ditegaskan pada topik perulangan dan topik iterator.

reserve dan biaya realokasi#

Member function reserve() menyatakan kapasitas minimum yang perlu disiapkan tanpa menambahkan satu elemen pun, sehingga size() tidak berubah. Apabila banyaknya elemen akhir sudah diketahui, satu pemanggilan reserve() menggantikan beberapa realokasi yang terjadi saat pengisian berlangsung. Setiap realokasi yang terhindar berarti satu pemindahan seluruh elemen yang tidak perlu terjadi.

cpp reserve.cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> tanpaReserve;
    for (int i = 1; i <= 10; ++i) {
        tanpaReserve.push_back(i);
    }
    std::cout << "Tanpa reserve : size = " << tanpaReserve.size()
              << ", capacity = " << tanpaReserve.capacity() << '\n';

    std::vector<int> denganReserve;
    denganReserve.reserve(10);
    for (int i = 1; i <= 10; ++i) {
        denganReserve.push_back(i);
    }
    std::cout << "Dengan reserve: size = " << denganReserve.size()
              << ", capacity = " << denganReserve.capacity() << '\n';
    return 0;
}
text Keluaran
Tanpa reserve : size = 10, capacity = 16
Dengan reserve: size = 10, capacity = 10

Kedua vector berisi sepuluh elemen yang sama, tetapi vector yang di-reserve() tidak menyiapkan ruang lebih dari yang diperlukan dan tidak pernah melakukan realokasi selama pengisian. Perbedaan ini terasa pada elemen bertipe besar, misalnya std::string, karena setiap realokasi menyalin seluruh isinya. Untuk elemen int berjumlah sepuluh, selisihnya tidak terasa, tetapi kebiasaan menulisnya sejak awal tetap berguna.

reserve() tidak boleh disamakan dengan resize(). Fungsi resize(n) benar-benar mengubah size(), yaitu menambah elemen bernilai nol atau menghapus elemen yang berlebih, sedangkan reserve(n) hanya menyiapkan kapasitas. Kesalahan paling umum adalah berharap reserve() membuat elemen bisa diakses lewat indeks, padahal elemen tersebut belum ada.

Kapasitas yang sudah membesar tidak menyusut dengan sendirinya. pop_back() dan clear() menurunkan size(), sedangkan capacity() tetap pada nilai terakhirnya, sehingga memori tidak langsung dilepaskan. Permintaan pengembalian memori ke sistem dapat diajukan melalui shrink_to_fit(), dan pustaka bebas memenuhi maupun menolak permintaan tersebut.

Kesalahan umum#

Tiga kesalahan berikut paling sering muncul pada pemakaian std::vector yang baru dipelajari.

  1. Mengakses angka[10] pada vector berisi empat elemen. Operator [] tidak memeriksa batas, sehingga aksesnya berujung pada undefined behavior. Gunakan at(), yang melempar std::out_of_range dan dapat ditangkap seperti pada topik std::array, ketika pemeriksaan batas memang diperlukan.
  2. Mencampuradukkan reserve() dan resize(). reserve() hanya mengubah kapasitas, sedangkan resize() mengubah ukuran dan mengisi elemennya. Tanyakan tujuannya lebih dahulu, yaitu menyiapkan ruang atau menambah elemen, kemudian pilih fungsinya.
  3. Berharap capacity menyusut setelah pop_back() atau penghapusan. Kapasitas tetap utuh agar penambahan berikutnya tidak perlu realokasi, sehingga pengurangan memori memerlukan permintaan eksplisit shrink_to_fit() yang boleh ditolak pustaka.

Kesalahan-kesalahan tersebut jarang menghasilkan error yang jelas, sehingga pencegahannya lebih bergantung pada kebiasaan daripada pesan compiler. Menanyakan tiga hal secara teratur sudah cukup, yaitu berapa banyak elemen yang tersimpan, berapa ruang yang sudah disiapkan, dan apakah akses indeksnya berada di dalam batas. Ketiganya dapat dijawab dengan size(), capacity(), dan at() tanpa menambah biaya berarti.

Ringkasan#

  • std::vector menyimpan elemen berurutan dengan ukuran yang berubah, dan push_back() beserta pop_back() menjadi operasi tambah dan hapus di ujungnya.
  • size() menyatakan banyaknya elemen, sedangkan capacity() menyatakan ruang yang sudah dialokasikan, dan realokasi terjadi ketika ruang tersebut habis.
  • reserve() menyiapkan kapasitas tanpa menambah elemen, sehingga pengisian dengan jumlah yang sudah diketahui tidak memicu realokasi berulang.

Referensi#