Pada Chapter std::any kita menutup rangkaian tipe utilitas nilai; chapter ini beralih ke utilitas yang bekerja pada kumpulan data. Bayangkan kita menulis total(const std::vector<int>&), lalu teman satu tim memanggilnya dengan std::array dan yang lain dengan array C — kita terpaksa menulis tiga overload atau memaksa pemanggil menyalin datanya. std::span<T> (header <span>, sejak C++20) menyelesaikan masalah ini: ia adalah non-owning view, yaitu jendela baca (dan bila diminta, tulis) ke data yang tersimpan berurutan (contiguous) di memori, tanpa menyalin dan tanpa memiliki data itu. Karena std::vector, std::array, dan array C semuanya tersimpan kontigu, satu parameter span menerima ketiganya sekaligus. Chapter ini membahas konsep jendela itu, pemakaiannya sebagai parameter fungsi generik, pemotongan dengan subspan, dan satu jebakan masa hidup yang wajib kita kenali.
Jendela yang Tidak Memiliki Datanya #
Di balik layar, span hanya menyimpan dua hal: pointer ke elemen pertama dan jumlah elemen. Karena itu membuat span sangat murah — tidak ada alokasi memori dan tidak ada penyalinan elemen; biayanya setara dengan melewatkan pointer plus panjang secara manual. Bedanya, span membawa panjang di dalam objeknya sendiri, sehingga range-based for bisa berjalan tanpa parameter ukuran terpisah dan size() selalu tersedia. Sifat inilah yang membuat span disebut view: ia mengamati data milik pihak lain dan tidak pernah memperpanjang masa hidupnya.
Ada dua bentuk yang paling sering dipakai: std::span<T> boleh mengubah elemen yang dilihatnya, sedangkan std::span<const T> hanya bisa membaca. Aturan praktisnya sama seperti memilih const std::vector<T>& untuk fungsi baca-saja: pilih span<const T> supaya kontrak “kita tidak mengubah data Anda” tertulis langsung pada tipe parameter. std::vector, std::array, dan array C semuanya berkonversi otomatis ke span yang cocok saat dilewatkan ke fungsi — konversi implisit inilah yang membuat satu fungsi cukup untuk tiga sumber data sekaligus.
Satu Fungsi untuk vector, array, dan Array C #
Program berikut mendemonstrasikan klaim di atas: tiga fungsi pendek, tiga sumber data berbeda, tanpa satu pun penyalinan elemen.
#include <array> // std::array
#include <iostream> // std::cout
#include <span> // std::span (C++20)
#include <vector> // std::vector
// span<const int>: menerima vector, array, maupun C-array — tanpa salinan
int total(std::span<const int> data) {
int jumlah = 0;
for (int x : data) jumlah += x;
return jumlah;
}
void cetak(std::span<const int> data) {
std::cout << "[";
for (std::size_t i = 0; i < data.size(); ++i) {
if (i > 0) std::cout << ", ";
std::cout << data[i];
}
std::cout << "]";
}
// span<int> (tanpa const): boleh MENGUBAH elemen milik pemanggil
void kuadratkan(std::span<int> data) {
for (int& x : data) x = x * x;
}
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
std::array<int, 4> a = {10, 20, 30, 40};
int c[] = {7, 8, 9}; // array C-style biasa
// satu fungsi, tiga sumber data
std::cout << "total vector : " << total(v) << "\n";
std::cout << "total array : " << total(a) << "\n";
std::cout << "total c-array: " << total(c) << "\n";
// span hanyalah jendela: mengubah lewat span mengubah data aslinya
std::span<int> jendela = v;
jendela[0] = 100;
std::cout << "v[0] lewat span: " << v[0] << "\n";
// potongan tanpa salinan: subspan, first, last
std::cout << "v : "; cetak(v); std::cout << "\n";
std::cout << "subspan(1, 3) : "; cetak(jendela.subspan(1, 3)); std::cout << "\n";
std::cout << "first(2) : "; cetak(jendela.first(2)); std::cout << "\n";
std::cout << "last(2) : "; cetak(jendela.last(2)); std::cout << "\n";
// fungsi mengubah vector asli lewat span
kuadratkan(v);
std::cout << "dikuadratkan : "; cetak(v); std::cout << "\n";
std::cout << "size/front/back: " << jendela.size() << " "
<< jendela.front() << " " << jendela.back() << "\n";
return 0;
}Output yang Dihasilkan:
total vector : 15
total array : 100
total c-array: 24
v[0] lewat span: 100
v : [100, 2, 3, 4, 5]
subspan(1, 3) : [2, 3, 4]
first(2) : [100, 2]
last(2) : [4, 5]
dikuadratkan : [10000, 4, 9, 16, 25]
size/front/back: 5 10000 25Mari kita bedah:
- Baris 7 (
int total(std::span<const int> data)): parameter span baca-saja menggantikan tiga overload lama.datadipakai seperti container biasa — range-based for dansize()bekerja tanpa tahu asal datanya. - Baris 13-20 (
cetak): fungsi utilitas yang memakaisize()danoperator[]milik span. Perhatikan pemisah", "diletakkan sebelum elemen (kecuali elemen pertama) agar tidak ada spasi tersisa di ujung baris. - Baris 23-25 (
kuadratkan): parameterstd::span<int>tanpaconstmemberi izin mengubah. Setiapxdi sini adalah referensi ke elemen milik pemanggil, bukan salinan. - Baris 28-30 (tiga sumber data):
std::vector,std::array<int, 4>, dan array Cint c[]. Ketiganya tersimpan kontigu — syarat tunggal untuk menjadi milik sebuah span. - Baris 33-35: satu fungsi
total, tiga pemanggilan dengan tipe argumen berbeda; konversi kestd::span<const int>terjadi otomatis tanpa menyalin satu elemen pun. - Baris 38-40 (
jendela[0] = 100): membuat span dari vector lalu mengubah elemen lewat span. Hasilnya terbaca div[0]— jendela menunjuk data asli, bukan menyalinnya. - Baris 43-46 (potongan):
subspan(1, 3)mengambil tiga elemen mulai indeks 1,first(2)dua elemen awal,last(2)dua elemen akhir — semuanya span baru tanpa penyalinan. - Baris 49-52:
kuadratkan(v)mengubah vector asli lewat konversi span, dansize()/front()/back()melaporkan kondisi terkini: 5 elemen, depan10000, belakang25.
Potongan Tanpa Salinan dengan subspan #
Keluarga pemotongan span punya pola yang konsisten dan mudah diingat. subspan(offset, count) mengambil count elemen mulai indeks offset, sedangkan subspan(offset) tanpa argumen kedua mengambil semua elemen sampai akhir. first(n) dan last(n) masing-masing mengambil potongan dari depan dan dari belakang. Semua fungsi ini mengembalikan span baru yang tetap menunjuk data asli — pointer digeser, ukuran diperbarui, dan tidak ada elemen yang disalin.
Span juga punya sisi kekuatan pada waktu kompilasi. std::span<int> default memiliki ukuran yang ditentukan saat runtime (dynamic extent), tetapi kita bisa menulis std::span<int, 4> untuk menjanjikan panjang tepat 4 — ukuran menjadi bagian dari tipe, dan compiler bisa memeriksa kesalahannya lebih awal. Sebaliknya, potongan yang melewati batas adalah undefined behavior: subspan(10, 2) pada span berukuran 5 tidak melempar exception, jadi periksa size() terlebih dahulu bila offset datang dari input yang tidak kita kendalikan.
Span Tidak Menjaga Data Tetap Hidup #
Satu jebakan terpenting: span tidak memiliki datanya, sehingga ia bisa menunjuk data yang sudah tidak ada. Pola yang paling sering terjadi adalah mengembalikan span ke variabel lokal — variabel itu hancur saat fungsi berakhir, dan span yang kembali menjadi dangling view ke memori yang sudah dikembalikan.
std::span<int> buatJendela() {
std::vector<int> lokal = {1, 2, 3};
return std::span<int>(lokal); // jendela ke vector lokal
} // lokal hancur di sini
int main() {
std::span<int> s = buatJendela();
// std::cout << s[0]; // undefined behavior: data di bawahnya sudah tiada
}Aturan amannya sederhana: pastikan pemilik data hidup lebih lama daripada span-nya. Span paling aman dipakai sebagai parameter fungsi dan variabel lokal berumur pendek, bukan sebagai anggota class yang disimpan lama — kecuali kita yakin pemiliknya tetap hidup. Dengan aturan itu, span memberi kita fleksibilitas penuh tanpa biaya apa pun.
Dengan span, cara kita menulis fungsi yang menerima deret data berubah total: satu parameter untuk semua sumber kontigu. Chapter berikutnya berpindah dari jendela data ke deret yang paling dasar bagi komputer: std::bitset, kumpulan bit berukuran tetap yang ideal untuk flag dan operasi bitwise.
Rujukan resmi: cppreference — std::span , cppreference — header <span> , cppreference — span::subspan .