Judul chapter ini memuat kata “idiom” dengan sengaja: menghapus elemen dari std::vector berdasarkan nilai ternyata tidak bisa dilakukan satu algoritma saja sebelum C++20. Penyebabnya terletak pada pembagian tugas yang ketat di STL: algoritma bekerja pada rentang iterator dan tidak tahu apa-apa tentang container, sedangkan hanya container yang bisa mengubah ukurannya sendiri. Akibatnya std::remove tidak menghapus apa pun — ia hanya menggeser elemen yang lolos ke depan rentang. Chapter ini membedah perilaku tersebut, merakit idiom klasik erase(remove(...)), lalu berkenalan dengan std::erase_if dari C++20 yang memangkas seluruh kegembengan itu menjadi satu panggilan.
std::remove Tidak Menghapus Apa pun #
Perilaku std::remove(first, last, nilai): elemen yang tidak sama dengan nilai digeser maju berurutan ke depan rentang, lalu fungsi mengembalikan iterator ke akhir rentang logis yang baru. Ukuran container tidak berubah sama sekali; elemen setelah akhir rentang logis tetap ada secara fisik, meskipun isinya tidak lagi dijamin bermakna. Program berikut membuktikannya:
#include <algorithm>
#include <iostream>
#include <vector>
void cetak(const std::vector<int>& v) {
for (std::size_t i = 0; i < v.size(); ++i) {
if (i > 0) std::cout << ' ';
std::cout << v[i];
}
std::cout << '\n';
}
int main() {
std::vector<int> data = {4, 8, 6, 8, 2, 8, 9};
std::cout << "Sebelum : ";
cetak(data);
std::cout << "size awal : " << data.size() << "\n";
// std::remove hanya MENGGESER elemen: yang lolos maju ke depan
auto logis = std::remove(data.begin(), data.end(), 8);
// Rentang logis [begin, logis) berisi elemen yang tersisa
std::cout << "Setelah remove: ";
for (auto it = data.begin(); it != logis; ++it) {
if (it != data.begin()) std::cout << ' ';
std::cout << *it;
}
std::cout << "\n";
std::cout << "size tetap : " << data.size() << "\n";
// erase: benar-benar menghapus sisa [logis, end)
data.erase(logis, data.end());
std::cout << "Setelah erase : ";
cetak(data);
std::cout << "size sekarang : " << data.size() << "\n";
return 0;
}Output yang Dihasilkan:
Sebelum : 4 8 6 8 2 8 9
size awal : 7
Setelah remove: 4 6 2 9
size tetap : 7
Setelah erase : 4 6 2 9
size sekarang : 4Bedah baris demi baris:
- Baris 15-16: data awal
{4, 8, 6, 8, 2, 8, 9}dengansizetujuh. - Baris 19 (
std::remove(data.begin(), data.end(), 8)): setiap elemen yang bukan8digeser maju. Hasilnya rentang logis4 6 2 9, danlogisadalah iterator ke posisi setelah elemen terakhir yang tersisa. - Baris 22-27: kita mencetak hanya
[data.begin(), logis)— rentang yang dijamin standar. Yang berada setelahlogissengaja tidak dicetak karena standar tidak menjamin isinya; pada GCC 14 nilai lama memang masih terlihat, tetapi mengandalkannya adalah kesalahan. - Baris 29:
sizemasih tujuh. Inilah bukti bahwaremovetidak menyentuh ukuran container. - Baris 32-33 (
data.erase(logis, data.end())): membererasemilik vector-lah yang benar-benar menghapus, dansizeakhirnya turun menjadi empat.
Pemisahan tugas ini konsisten dengan desain STL: remove hanya butuh iterator dan bisa bekerja di array C-style yang tidak bisa mengubah ukuran, sedangkan mengubah ukuran adalah wewenang container. Harga bayarannya adalah dua langkah untuk satu maksud.
Idiom erase(remove(…)) #
Kedua langkah itu dirangkai menjadi satu pernyataan yang dikenal luas sebagai remove-erase idiom:
v.erase(std::remove_if(v.begin(), v.end(), predikat), v.end());Bacaannya dari dalam ke luar: remove_if menggeser elemen yang lolos predikat ke depan dan mengembalikan akhir rentang logis; erase kemudian menghapus dari posisi itu sampai v.end(). Hasil akhirnya: elemen yang lolos predikat hilang dari container, size menyusut, dan urutan elemen tersisa dipertahankan. Program berikut memakai idiom itu lalu lanjut ke penggantinya:
#include <algorithm>
#include <iostream>
#include <vector>
void cetak(const std::vector<int>& v) {
for (std::size_t i = 0; i < v.size(); ++i) {
if (i > 0) std::cout << ' ';
std::cout << v[i];
}
std::cout << '\n';
}
int main() {
std::vector<int> data = {5, 12, 8, 15, 3, 20, 7};
// Idiom klasik: erase(remove_if(...)) dalam satu pernyataan
data.erase(std::remove_if(data.begin(), data.end(),
[](int x) { return x % 2 != 0; }),
data.end());
std::cout << "erase(remove_if) : ";
cetak(data);
// Modernisasi C++20: std::erase_if, satu panggilan tanpa idiom
std::erase_if(data, [](int x) { return x > 10; });
std::cout << "erase_if : ";
cetak(data);
return 0;
}Output yang Dihasilkan:
erase(remove_if) : 12 8 20
erase_if : 8Baris pertama output menunjukkan vector setelah semua elemen ganjil dihapus lewat idiom klasik ({12, 8, 20}), dan baris kedua setelah std::erase_if(data, [](int x) { return x > 10; }) menghapus semua yang di atas sepuluh, menyisakan {8}. Idiom klasik dan erase_if menghasilkan efek yang identik; yang berbeda hanya jumlah kode dan peluang salah tulis.
erase_if: Modernisasi C++20 #
Karena pola idiom itu mudah salah tulis dan tidak sedikit pula yang tertukar antara remove, remove_if, dan argumen erase, standar C++20 menambahkan std::erase_if(c, predikat) serta std::erase(c, nilai) sebagai overload khusus container: vector, string, list, map, set, dan lainnya. Fungsi ini milik header container yang bersangkutan (untuk vector, <vector>), menerima container langsung, mengubah ukurannya, dan mengembalikan jumlah elemen yang terhapus.
Sampai di sini kita sudah bisa menambah, menyalin, mengubah, dan mengurangi elemen. Chapter berikutnya melangkah ke algoritma pengelompokan: std::partition yang membagi rentang menjadi dua kelompok menurut predikat tanpa perlu menyortir penuh.
Rujukan resmi: std::remove dan remove_if serta std::erase_if untuk vector .