C++ menyediakan tiga cara untuk membuat array: C-style array (int arr[N]), std::array<T, N>, dan std::vector<T>. Ketiganya memiliki layout memori yang identik — blok kontigu — namun berbeda dalam cara pengelolaan ukuran, lokasi memori, dan antarmuka. Pada Chapter ini kita akan membandingkan ketiga opsi secara head-to-head, mulai dari sifat decay, lokasi alokasi, hingga kemampuan di-copy. Pemilihan yang tepat antara statis dan dinamis berdampak pada keamanan memori, kinerja cache, dan kejelasan kode.
Tiga Wajah Array di C++ #
Sebelum masuk ke kode, mari petakan perbedaan utama:
| Aspek | C-style array int arr[N] | std::array<T, N> | std::vector<T> |
|---|---|---|---|
| Ukuran | Tetap, konstan | Tetap, konstan | Dinamis (runtime) |
| Lokasi memori | Stack | Stack | Heap (default) |
| Tahu ukurannya sendiri | Hanya di scope asal | Ya (size()) | Ya (size()) |
| Decay ke pointer | Ya | Tidak | Tidak |
Bisa di-assign = | Tidak | Ya | Ya |
| Iterator STL | Tidak langsung | Ya | Ya |
| Bisa diubah ukurannya | Tidak | Tidak | Ya (resize) |
| Overhead memori | 0 | 0 | Beberapa byte untuk metadata |
Tabel di atas menunjukkan bahwa std::array adalah perbaikan langsung C-style array — tetap di stack, tetap ukuran tetap, namun aman dari decay dan mendukung antarmuka STL. std::vector menambahkan dimensi baru: ukuran dinamis dengan biaya beberapa byte overhead.
C-style Array: Ringan tapi Rapuh #
Mekanisme: Decay ke Pointer #
C-style array dideklarasikan dengan T nama[N]. Saat dilewatkan ke fungsi, tipenya meluruh menjadi T* — informasi ukuran hilang. Akibatnya, fungsi penerima tidak dapat mengetahui panjang array. Inilah sumber banyak bug klasik C/C++.
Kode: C-style Array dan Decay #
#include <iostream>
void cetak_ukuran(int arr[]) {
// arr di sini bertipe int*, bukan int[5]
std::cout << "di fungsi: sizeof(arr) = " << sizeof(arr) << '\n';
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
std::cout << "di main: sizeof(arr) = " << sizeof(arr) << '\n';
cetak_ukuran(arr);
// Tidak bisa di-assign langsung
int lain[5];
// lain = arr; // ERROR: tidak ada operator= untuk C-style array
return 0;
}Output yang Dihasilkan:
di main: sizeof(arr) = 20
di fungsi: sizeof(arr) = 8- Baris
int arr[5] = {1,2,3,4,5};: array 5 elemen dialokasikan di stack. Total memori5 * 4 = 20byte. - Baris
sizeof(arr)dimain:arrmasih berupa array asli sehinggasizeofmengembalikan 20 byte. - Baris
sizeof(arr)dicetak_ukuran:arrsudah meluruh menjadiint*.sizeof(int*)= 8 byte pada sistem 64-bit. Fungsi kehilangan informasi ukuran array. - Baris
// lain = arr;: C-style array tidak mendukung assignment langsung — operator=tidak didefinisikan untuknya. Komentar menandai baris yang akan error bila di-uncomment.
std::array: C-style Array yang Aman #
Mekanisme: Pembungkus Tipis #
std::array<T, N> adalah pembungkus tipis di atas C-style array. Tidak ada overhead memori (sizeof sama dengan array C asli), namun menyediakan antarmuka STL: size(), at(), begin(), end(), front(), back(). Karena tidak decay ke pointer, std::array dapat di-assign dengan =.
Kode: std::array dengan Iterator dan Bounds Checking #
#include <iostream>
#include <array>
void cetak(const std::array<int, 5>& arr) {
// Tidak decay — tahu ukuran di mana saja
std::cout << "ukuran = " << arr.size() << '\n';
for (int x : arr) std::cout << ' ' << x;
std::cout << '\n';
}
int main() {
std::array<int, 5> a = {1, 2, 3, 4, 5};
std::array<int, 5> b;
b = a; // OK: std::array mendukung assignment
std::cout << "a: "; cetak(a);
std::cout << "b: "; cetak(b);
// Akses aman dengan bounds checking
try {
std::cout << "b.at(2) = " << b.at(2) << '\n';
std::cout << "b.at(10) = " << b.at(10) << '\n'; // melempar
} catch (const std::out_of_range& e) {
std::cout << "exception: " << e.what() << '\n';
}
return 0;
}Output yang Dihasilkan:
a: ukuran = 5
1 2 3 4 5
b: ukuran = 5
1 2 3 4 5
b.at(2) = 3
exception: array::at: __n (which is 10) >= _Nm (which is 5)- Baris
std::array<int, 5>: parameter template<Tipe, Ukuran>. Ukuran harus konstan kompilasi — tidak bisa ditentukan saat runtime. - Baris
b = a;: assignment antarstd::arrayberukuran sama menyalin seluruh elemen. C-style array tidak mendukung operasi ini. - Baris
arr.size(): mengembalikan jumlah elemen. Berbeda darisizeofC-style,size()bekerja bahkan setelah array dilewatkan ke fungsi. - Baris
b.at(10):at()melakukan bounds checking. Bila indeks di luar[0, N), lemparstd::out_of_range. Operator[]tidak melakukan ini — efisien tapi tidak aman.
std::vector: Dinamis dan Fleksibel #
Mekanisme: Alokasi Heap dengan Manajemen Otomatis #
std::vector<T> menyimpan elemen pada memori heap (default) yang dialokasikan dan dibebaskan secara otomatis. Ukuran dapat berubah saat runtime melalui push_back, pop_back, resize, dan insert. Internal std::vector mempertahankan dua nilai: size (jumlah elemen aktif) dan capacity (ruang yang dialokasikan).
Kode: std::vector dengan Pertumbuhan Otomatis #
#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 <= 5; ++i) {
v.push_back(i * 10);
std::cout << "setelah push_back(" << i*10 << "): "
<< "size=" << v.size()
<< " capacity=" << v.capacity() << '\n';
}
// Resize: ubah jumlah elemen
v.resize(3);
std::cout << "setelah resize(3): size=" << v.size() << '\n';
// Reserve: alokasi tanpa mengubah size
v.reserve(100);
std::cout << "setelah reserve(100): size=" << v.size()
<< " capacity=" << v.capacity() << '\n';
return 0;
}Output yang Dihasilkan:
awal: size=0 capacity=0
setelah push_back(10): size=1 capacity=1
setelah push_back(20): size=2 capacity=2
setelah push_back(30): size=3 capacity=4
setelah push_back(40): size=4 capacity=4
setelah push_back(50): size=5 capacity=8
setelah resize(3): size=3
setelah reserve(100): size=3 capacity=100- Baris
std::vector<int> v;: vector kosong.size = 0dancapacity = 0— tidak ada alokasi. - Baris
push_back(10): alokasi pertama berkapasitas 1. Setiap kalisizeakan melebihicapacity, vector mengalokasikan ulang dengan kapasitas baru (umumnya 2×) dan menyalin elemen lama. - Baris
v.resize(3): mengubahsizemenjadi 3. Elemen berlebih dihapus; elemen kurang ditambah dengan nilai default. - Baris
v.reserve(100): mengalokasikan ruang untuk 100 elemen tanpa mengubahsize. Berguna bila kita tahu ukuran akhir — menghindari realokasi berulang.
Memilih dengan Bijak #
Tabel keputusan berikut membantu kita memilih struktur yang tepat berdasarkan kebutuhan:
| Kebutuhan | Pilihan | Alasan |
|---|---|---|
| Ukuran diketahui saat kompilasi, di stack | std::array | Tanpa overhead heap, aman dari decay |
| Ukuran berubah saat runtime | std::vector | Pertumbuhan otomatis, antarmuka lengkap |
| Kompatibilitas dengan API C | C-style array | Beberapa API C hanya menerima T* |
| Interoperabilitas dengan STL | std::array / std::vector | Mendukung iterator dan range-based for |
| Array besar (> 1 juta elemen) | std::vector | Stack overflow bila pakai std::array |
Ringkasan #
- C-style array ringan namun rapuh: decay ke pointer saat dilewatkan ke fungsi dan tidak tahu ukurannya sendiri.
std::array<T, N>adalah pengganti modern untuk C-style array: aman dari decay, mendukung assignment, dan antarmuka STL, tanpa overhead memori.std::vector<T>menyediakan ukuran dinamis dengan alokasi otomatis di heap — pilihan default untuk DSA.- Gunakan
.at()untuk bounds checking selama pengembangan; ganti ke[]untuk kinerja setelah logika teruji. - Untuk parameter fungsi yang menerima array kontigu sembarang, gunakan
std::span(C++20) atau reference kestd::vector/std::array.