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:

AspekC-style array int arr[N]std::array<T, N>std::vector<T>
UkuranTetap, konstanTetap, konstanDinamis (runtime)
Lokasi memoriStackStackHeap (default)
Tahu ukurannya sendiriHanya di scope asalYa (size())Ya (size())
Decay ke pointerYaTidakTidak
Bisa di-assign =TidakYaYa
Iterator STLTidak langsungYaYa
Bisa diubah ukurannyaTidakTidakYa (resize)
Overhead memori00Beberapa 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 #

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

text
di main: sizeof(arr) = 20
di fungsi: sizeof(arr) = 8
  1. Baris int arr[5] = {1,2,3,4,5};: array 5 elemen dialokasikan di stack. Total memori 5 * 4 = 20 byte.
  2. Baris sizeof(arr) di main: arr masih berupa array asli sehingga sizeof mengembalikan 20 byte.
  3. Baris sizeof(arr) di cetak_ukuran: arr sudah meluruh menjadi int*. sizeof(int*) = 8 byte pada sistem 64-bit. Fungsi kehilangan informasi ukuran array.
  4. 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 #

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

text
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)
  1. Baris std::array<int, 5>: parameter template <Tipe, Ukuran>. Ukuran harus konstan kompilasi — tidak bisa ditentukan saat runtime.
  2. Baris b = a;: assignment antar std::array berukuran sama menyalin seluruh elemen. C-style array tidak mendukung operasi ini.
  3. Baris arr.size(): mengembalikan jumlah elemen. Berbeda dari sizeof C-style, size() bekerja bahkan setelah array dilewatkan ke fungsi.
  4. Baris b.at(10): at() melakukan bounds checking. Bila indeks di luar [0, N), lempar std::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 #

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

text
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
  1. Baris std::vector<int> v;: vector kosong. size = 0 dan capacity = 0 — tidak ada alokasi.
  2. Baris push_back(10): alokasi pertama berkapasitas 1. Setiap kali size akan melebihi capacity, vector mengalokasikan ulang dengan kapasitas baru (umumnya 2×) dan menyalin elemen lama.
  3. Baris v.resize(3): mengubah size menjadi 3. Elemen berlebih dihapus; elemen kurang ditambah dengan nilai default.
  4. Baris v.reserve(100): mengalokasikan ruang untuk 100 elemen tanpa mengubah size. Berguna bila kita tahu ukuran akhir — menghindari realokasi berulang.

Memilih dengan Bijak #

Tabel keputusan berikut membantu kita memilih struktur yang tepat berdasarkan kebutuhan:

KebutuhanPilihanAlasan
Ukuran diketahui saat kompilasi, di stackstd::arrayTanpa overhead heap, aman dari decay
Ukuran berubah saat runtimestd::vectorPertumbuhan otomatis, antarmuka lengkap
Kompatibilitas dengan API CC-style arrayBeberapa API C hanya menerima T*
Interoperabilitas dengan STLstd::array / std::vectorMendukung iterator dan range-based for
Array besar (> 1 juta elemen)std::vectorStack 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 ke std::vector / std::array.