Chapter sebelumnya memakai size() tanpa menyingkap apa yang terjadi di belakang vector saat elemen bertambah. Padahal vector menyimpan dua angka: size, jumlah elemen yang benar-benar ada, dan capacity, jumlah elemen yang dapat ditampung sebelum penyimpanan harus ditata ulang. Perbedaan kedua angka ini menjelaskan mengapa push_back yang sering dipanggil tetap cepat, dan memberi kita alat untuk menyetel performa lewat reserve. Pada Chapter ini kita mengamati keduanya secara langsung di platform kita, sambil mencatat bahwa angka pertumbuhan boleh berbeda antar implementasi compiler.
Size vs Capacity #
size adalah berapa banyak elemen yang ada sekarang; capacity adalah berapa banyak elemen yang muat sebelum vector perlu memindahkan datanya ke blok memori yang lebih besar. Elemen yang “ruang kosong” antara size dan capacity tidak boleh diakses — hanya bagian [0, size) yang sah dibaca atau ditulis.
#include <iostream>
#include <vector>
int main() {
std::vector<int> v;
std::cout << "awal : size=" << v.size()
<< " capacity=" << v.capacity() << "\n";
for (int i = 1; i <= 10; ++i) {
v.push_back(i * 10);
std::cout << "push_back " << i * 10
<< " -> size=" << v.size()
<< " capacity=" << v.capacity() << "\n";
}
// reserve menyiapkan kapasitas lebih awal
std::vector<int> w;
w.reserve(10);
std::cout << "setelah reserve : size=" << w.size()
<< " capacity=" << w.capacity() << "\n";
// menambah 10 elemen: capacity tidak bertambah lagi
for (int i = 1; i <= 10; ++i) {
w.push_back(i);
}
std::cout << "isi 10 elemen : size=" << w.size()
<< " capacity=" << w.capacity() << "\n";
return 0;
}Output yang Dihasilkan:
awal : size=0 capacity=0
push_back 10 -> size=1 capacity=1
push_back 20 -> size=2 capacity=2
push_back 30 -> size=3 capacity=4
push_back 40 -> size=4 capacity=4
push_back 50 -> size=5 capacity=8
push_back 60 -> size=6 capacity=8
push_back 70 -> size=7 capacity=8
push_back 80 -> size=8 capacity=8
push_back 90 -> size=9 capacity=16
push_back 100 -> size=10 capacity=16
setelah reserve : size=0 capacity=10
isi 10 elemen : size=10 capacity=10Mari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 7-8: vector yang baru dibuat memulai dengan
size=0dancapacity=0— belum ada penyimpanan yang dialokasikan. - Baris 10-15 (loop
push_back): setiap kalisizemencapaicapacity, vector mengalokasikan blok memori baru yang lebih besar (pola pada GCC di sini: 1, 2, 4, 8, 16 — tumbuh dua kali lipat), menyalin elemen lama, lalu melepas blok lama. Perhatikan bahwacapacitytidak berubah di antara titik-titik tumbuh tersebut. - Baris 18-21 (
reserve(10)): kapasitas dipastikan setidaknya 10 sebelum satu elemen pun ditambahkan.sizetetap 0 karena reserve hanya menyiapkan ruang, bukan mengisi elemen. - Baris 24-26: sepuluh kali
push_backberjalan tanpa satu pun perpindahan memori karena ruang sudah tersedia — inilah manfaat utamareserve.
Bagaimana Penataan Ulang Terjadi #
Saat capacity habis dan push_back dipanggil, vector melakukan empat langkah: mengalokasikan blok baru yang lebih besar, menyalin atau memindahkan seluruh elemen ke blok baru, membebaskan blok lama, dan mengarahkan penyimpanan internalnya ke blok baru. Konsekuensi pentingnya: semua pointer, reference, dan iterator ke elemen vector menjadi tidak sah setelah penataan ulang, karena alamat elemen ikut pindah.
std::vector<int> v = {1, 2, 3};
int* p = &v[0]; // menunjuk elemen pertama
v.push_back(4); // memicu penataan ulang (dapat saja terjadi)
// p kini TIDAK SAH — menunjuk blok memori yang sudah dilepas
Snippet di atas adalah pola yang harus dihindari, bukan contoh yang dijalankan: menyentuh p setelah push_back adalah undefined behavior. Aturan amannya sederhana — setelah operasi yang dapat menata ulang (push_back, insert, resize), anggap semua pointer dan iterator ke vector itu basi, dan ambil ulang alamatnya bila masih diperlukan. std::vector bahkan menyediakan reserve sebagai jalur preventif: cukup satu reserve sebelum loop panas, dan selama jumlah elemen tidak melewati kapasitas itu, alamat elemen dijamin stabil.
Memangkas dan Menyetel Kapasitas #
Dua operasi lain melengkapi pengelolaan kapasitas. shrink_to_fit() meminta vector memangkas kapasitasnya agar setara dengan size — permintaan ini non-binding, artinya implementasi boleh mengabaikannya. Selain itu, resize(n) mengubah size itu sendiri: elemen baru diberi nilai bawaan bila n lebih besar, atau elemen dibuang bila n lebih kecil.
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
v.resize(3);
std::cout << "resize(3) : size=" << v.size()
<< " capacity=" << v.capacity() << "\n";
v.push_back(99);
v.push_back(88);
std::cout << "push_back : size=" << v.size()
<< " capacity=" << v.capacity() << "\n";
v.shrink_to_fit();
std::cout << "shrink_to_fit: size=" << v.size()
<< " capacity=" << v.capacity() << "\n";
return 0;
}Output yang Dihasilkan:
resize(3) : size=3 capacity=5
push_back : size=5 capacity=5
shrink_to_fit: size=5 capacity=5Mari kita telaah: Baris 7 (resize(3)) membuang elemen keempat dan kelima sehingga size turun menjadi 3, tetapi capacity tetap 5 — membuang elemen tidak mengurangi alokasi. Baris 10-11 menambah dua elemen; karena ruang masih tersedia, capacity tidak berubah. Baris 14 (shrink_to_fit()) pada implementasi ini tidak menyusutkan apa pun karena size sudah sama dengan capacity — permintaannya sah, tetapi hasilnya bergantung implementasi, dan prosa program tidak boleh bergantung pada pengecilan kapasitas ini.
Dengan pemahaman size, capacity, dan penataan ulang, chapter berikutnya melengkapi kotak peralatan iterasi: cara berkeliling array dan vector dengan indeks, dengan range-based for, dan dengan iterator — tiga gaya yang masing-masing punya tempatnya.