Algoritma STL hanya mengenal satu bahasa: iterator. Ketika rentang tujuan berupa vector yang masih kosong, posisi di tengah list, atau sebuah stream, kita tidak selalu punya iterator yang pas — dan di sinilah iterator adapter berperan. Adapter adalah pembungkus tipis yang menerjemahkan satu operasi tulis iterator menjadi operasi lain: std::back_inserter memanggil push_back, std::front_inserter memanggil push_front, std::inserter memanggil insert pada posisi tertentu, sedangkan std::istream_iterator dan std::ostream_iterator menjadikan stream sumber dan tujuan algoritma. Pada Chapter ini kita mengenal keempat keluarga adapter tersebut, membongkar mekanismenya, dan menguji semuanya dalam dua program utuh.
back_inserter: Tujuan yang Tumbuh Sendiri #
Semua algoritma menulis hasil dengan pola yang sama: *tujuan = nilai, lalu maju satu langkah. Pola ini menuntut tempat yang sudah ada — algoritma tidak pernah memeriksa ukuran container tujuan, apalagi memperbesarnya. Menulis ke v.begin() pada vector kosong berarti menulis di luar batas dan menghasilkan undefined behavior. Keluarga algoritma std::copy akan kita bedah tuntas pada Chapter Algoritma copy dan move; fokus Chapter ini adalah sisi tujuannya.
std::back_inserter(c) menjawab masalah tempat itu. Helper dari header <iterator> ini menghasilkan objek std::back_insert_iterator yang menyimpan alamat container, dan setiap penulisan *it = nilai diterjemahkan menjadi c.push_back(nilai). Karena setiap elemen yang ditulis algoritma langsung ditambahkan ke akhir, tujuan tumbuh mengikuti jumlah elemen sumber tanpa satu panggilan resize pun. Semua container yang menyediakan push_back — std::vector, std::list, std::deque, std::string — bisa menjadi sasaran adapter ini.
Pipeline Stream dengan istream_iterator dan ostream_iterator #
Header <iterator> juga menyediakan dua adapter yang menjadikan stream anggota penuh ekosistem algoritma. std::istream_iterator<T> membaca satu objek T dari stream saat dibangun, lalu setiap ++ menarik nilai berikutnya; istream_iterator yang dibangun tanpa argumen berlaku sebagai iterator akhir yang menyatakan stream sudah habis. Sebaliknya, std::ostream_iterator<T>(stream, pembatas) mencetak setiap nilai yang ditulis ke stream, lalu menyambung teks pembatas. Kombinasi keduanya membentuk pipeline input-ke-output tanpa satu loop manual pun.
Program berikut memakai std::stringstream statis sebagai sumber data sehingga output tetap deterministik — istream_iterator tidak harus menunggu ketikan pengguna, karena kita bisa mengarahkannya ke teks yang sudah berisi angka. Aliran datanya: stream dibaca istream_iterator, dikumpulkan std::copy ke back_inserter, lalu dicetak kembali lewat ostream_iterator:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>
int main() {
// Sumber input statis berupa stringstream sehingga output deterministik
std::istringstream masukan("42 7 13 8 5");
// istream_iterator<int> membaca angka satu per satu dari stream
std::istream_iterator<int> awal(masukan);
std::istream_iterator<int> akhir; // iterator akhir: tanda stream habis
// back_inserter membuat vector tumbuh untuk setiap elemen yang ditulis
std::vector<int> angka;
std::copy(awal, akhir, std::back_inserter(angka));
std::cout << "Dibaca dari stream : ";
std::copy(angka.begin(), angka.end(),
std::ostream_iterator<int>(std::cout, " "));
std::cout << "(" << angka.size() << " angka)\n";
// fill_n + back_inserter: mengisi vector kosong tanpa resize manual
std::vector<int> daftarNol;
std::fill_n(std::back_inserter(daftarNol), 3, 0);
std::cout << "fill_n 3 x nol : ";
std::copy(daftarNol.begin(), daftarNol.end(),
std::ostream_iterator<int>(std::cout, " "));
std::cout << "(ukuran akhir " << daftarNol.size() << ")\n";
// istream_iterator<string>: alur data teks dibaca per kata
std::istringstream barisTeks("kita suka pipeline sederhana");
std::istream_iterator<std::string> kata(barisTeks);
std::istream_iterator<std::string> kataAkhir;
std::cout << "Kata dari stream : ";
std::copy(kata, kataAkhir, std::ostream_iterator<std::string>(std::cout, " "));
std::cout << "terbaca\n";
return 0;
}Output yang Dihasilkan:
Dibaca dari stream : 42 7 13 8 5 (5 angka)
fill_n 3 x nol : 0 0 0 (ukuran akhir 3)
Kata dari stream : kita suka pipeline sederhana terbacaBedah baris demi baris:
- Baris 10 (
std::istringstream masukan(...)): stringstream berisi teks"42 7 13 8 5"berperan persis sepertistd::cin— istream_iterator tidak peduli sumbernya stream apa pun. - Baris 13-14 (
awaldanakhir):awalmenunjuk angka pertama yang belum dibaca;akhirdibangun tanpa argumen sehingga menjadi penanda akhir stream. Pasangan ini membentuk rentang[awal, akhir)yang sah untukstd::copy. - Baris 18 (
std::copy(awal, akhir, std::back_inserter(angka))): setiap angka yang dibaca dari stream ditulis ke adapter, dan setiap penulisan menjadiangka.push_back(...)— vector berisi lima angka tanpa pernah kita sebut ukurannya. - Baris 21-23 (mencetak dengan ostream_iterator):
std::ostream_iterator<int>(std::cout, " ")mencetak tiap elemen disambung spasi. Baris 23 menambahkan teks penutup(5 angka)sehingga baris output tidak berakhir dengan pembatas — perilaku yang kita bahas di callout di bawah. - Baris 27 (
std::fill_n(std::back_inserter(daftarNol), 3, 0)):fill_nmenulis nilai0sebanyak tiga kali ke adapter, jadi vector kosong langsung berisi0 0 0. Adapter ini juga bekerja untukgenerate_ndan algoritma penulis lain. - Baris 34-36 (
istream_iterator<string>): tipe yang dibaca menentukan cara memotong stream;std::stringmengambil data per kata dengan pemisah spasi, sama seperti operator>>. - Baris 38 (copy kata ke
ostream_iterator<string>): rentang iterator yang dibangun dari dua istream_iterator berperilaku sama seperti rentang container — bukti bahwa algoritma tidak peduli asal iteratornya.
front_inserter dan inserter Pada Posisi Tertentu #
std::back_inserter selalu menulis di ujung belakang. Untuk ujung depan tersedia std::front_inserter(c) yang memanggil push_front, dan operasi itu hanya dimiliki container yang strukturnya mendukung penyisipan di depan dengan murah: std::deque dan std::list. Konsekuensinya perlu diantisipasi sejak awal: karena setiap elemen baru ditulis di depan, hasil penyalinan lewat front_inserter urutannya terbalik dari sumber.
Bentuk adapter yang paling umum adalah std::inserter(c, pos). Adapter ini menyimpan iterator posisi; setiap penulisan memanggil c.insert(pos, nilai) lalu menggeser posisi tersimpan ke elemen yang baru, sehingga penulisan beruntun tetap berurutan tanpa saling menindih. Pasangan klasiknya adalah menyisipkan nilai ke list terurut: posisi sisip diperoleh dari std::lower_bound — pencarian yang mengembalikan iterator ke elemen pertama yang tidak lebih kecil dari nilai, dan akan kita bedah pada Chapter Binary Search.
#include <algorithm>
#include <deque>
#include <iostream>
#include <iterator>
#include <list>
#include <vector>
int main() {
std::vector<int> sumber = {5, 30, 10, 25};
// 1. front_inserter: setiap tulisan memanggil push_front pada deque
std::deque<int> dek;
std::copy(sumber.begin(), sumber.end(), std::front_inserter(dek));
std::cout << "front_inserter : ";
std::copy(dek.begin(), dek.end(),
std::ostream_iterator<int>(std::cout, " "));
std::cout << "(urutan terbalik)\n";
// 2. inserter: sisip dua nilai ke list terurut pada posisi sisip
std::list<int> terurut = {10, 20, 40, 50};
auto posisi = std::lower_bound(terurut.begin(), terurut.end(), 26);
std::vector<int> baru = {25, 26};
std::copy(baru.begin(), baru.end(), std::inserter(terurut, posisi));
std::cout << "inserter : ";
std::copy(terurut.begin(), terurut.end(),
std::ostream_iterator<int>(std::cout, " "));
std::cout << "(tetap terurut)\n";
return 0;
}Output yang Dihasilkan:
front_inserter : 25 10 30 5 (urutan terbalik)
inserter : 10 20 25 26 40 50 (tetap terurut)Bedah baris demi baris:
- Baris 12-13 (copy ke front_inserter): sumber
5, 30, 10, 25diproses berurutan, tetapi setiap elemen masuk lewatpush_frontsehingga isi deque menjadi25, 10, 30, 5— pembalikan urutan yang memang dijanjikan mekanisme ini. - Baris 14-17 (mencetak isi deque):
std::ostream_iteratordipakai lagi di sini, memperlihatkan bahwa container hasil adapter bisa langsung dialirkan ke output tanpa loop manual. - Baris 21 (
std::lower_bound(..., 26)): pada list terurut10 20 40 50, fungsi ini mengembalikan iterator yang menunjuk40— posisi pertama yang tidak lebih kecil dari26. - Baris 22-23 (copy ke std::inserter): nilai
25disisipkan sebelum40, lalu posisi tersimpan maju ke elemen yang baru sehingga26disisipkan tepat setelah25. Hasil akhirnya10 20 25 26 40 50dan list tetap terurut. - Baris 15 dan 25 (pembatas
" "): kedua pencetakan kembali mengandalkan teks penutup(urutan terbalik)dan(tetap terurut)agar pembatas spasi tidak menggantung di akhir baris output.
Kaitan dengan Algoritma Output Iterator #
Pada Chapter Kategori Iterator kita mengenal kategori output iterator: hanya bisa ditulis, maju satu langkah, dan dirancang sekali jalan. Keempat adapter tulis di Chapter ini adalah penghuni nyata kategori tersebut — back_inserter, front_inserter, inserter, dan ostream_iterator tinggal di jenjang paling sederhana, dan justru di situlah nilainya: algoritma cukup menulis dan maju, tanpa perlu tahu container atau stream apa di baliknya. Inilah yang membuat algoritma tetap generik sampai ke tujuan yang paling tidak terduga.
Daftar algoritma yang menuntut output iterator panjang dan sering kita pakai: std::copy, std::copy_if, std::transform pada tujuannya, std::unique_copy, keluarga std::set_union dan std::set_difference, hingga std::fill_n dan std::generate_n yang kita uji di atas. Semua bisa diarahkan ke adapter mana pun tanpa perubahan kode — ganti back_inserter dengan ostream_iterator, dan hasil algoritma mengalir ke layar. Satu konsekuensi kategori ini perlu dipegang: adapter tulis tidak menjamin hasilnya bisa dibaca kembali, jadi jangan membaca *out dari adapter tulis.
Dengan adapter ini, rantai pengolahan bisa dirangkai dari sumber stream hingga container yang tumbuh sendiri. Chapter berikutnya, Algoritma Berbasis Rentang, memakai bekal yang sudah kita siapkan untuk membahas konvensi [first, last) yang dipakai seluruh algoritma STL.
Rujukan resmi: std::back_inserter , std::istream_iterator , dan std::ostream_iterator .