Keluarga algoritma penyalin adalah salah satu yang paling sering dipakai: std::copy menyalin seluruh rentang, copy_if menyalin yang lolos predikat, dan copy_n menyalin sejumlah elemen tertentu. Ketiganya punya satu syarat yang menjadi sumber bug klasik pemula: algoritma tidak akan memperbesar container tujuan — kita yang harus menyiapkan tempatnya, atau memakai std::back_inserter agar tujuan tumbuh otomatis. Selain menyalin, C++ juga punya algoritma std::move yang memindahkan elemen dengan menyerahkan isi objek sumber, menghindari penyalinan yang mahal. Chapter ini membahas semua itu sekaligus meluruskan kebingungan antara algoritma std::move dan utilitas std::move yang sama namanya.

std::copy dan Syarat Tujuan #

std::copy(first, last, d_first) menulis elemen sumber satu per satu ke posisi tujuan mulai dari d_first, menggeser d_first setiap kali menulis. Yang tidak dilakukannya: memeriksa apakah tujuan cukup besar. Bila tujuan adalah vector kosong dan kita mengirim v.begin() vector itu sebagai d_first, program menulis di luar batas — undefined behavior. Dua cara sah menyediakan tempat: buat tujuan dengan ukuran cukup (std::vector<int> salinan(6);), atau pakai back_inserter yang dibahas di bagian berikutnya.

cpp
#include <algorithm>
#include <iostream>
#include <iterator>
#include <string>
#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> sumber = {1, 2, 3, 4, 5, 6};

    // std::copy: salin [first, last) ke tujuan yang SUDAH cukup besar
    std::vector<int> salinan(6);
    std::copy(sumber.begin(), sumber.end(), salinan.begin());
    std::cout << "copy    : ";
    cetak(salinan);

    // copy_if + back_inserter: tujuan tumbuh otomatis
    std::vector<int> genap;
    std::copy_if(sumber.begin(), sumber.end(), std::back_inserter(genap),
                 [](int x) { return x % 2 == 0; });
    std::cout << "copy_if : ";
    cetak(genap);

    // copy_n: salin tepat n elemen dari awal
    std::vector<int> tiga;
    std::copy_n(sumber.begin(), 3, std::back_inserter(tiga));
    std::cout << "copy_n  : ";
    cetak(tiga);

    // std::move (algorithm): pindahkan elemen tanpa menyalin isinya
    std::vector<std::string> pesan = {"halo", "dunia"};
    std::vector<std::string> tujuan(2);
    std::move(pesan.begin(), pesan.end(), tujuan.begin());
    std::cout << "move    : " << tujuan[0] << ' ' << tujuan[1] << "\n";
    std::cout << "sumber  : "
              << (pesan[0].empty() ? "kosong" : pesan[0]) << "\n";
    return 0;
}

Output yang Dihasilkan:

text
copy    : 1 2 3 4 5 6
copy_if : 2 4 6
copy_n  : 1 2 3
move    : halo dunia
sumber  : kosong

Bedah baris demi baris:

  1. Baris 17-19 (std::vector<int> salinan(6) lalu std::copy): tujuan dibuat berisi enam elemen terlebih dahulu, lalu copy menimpanya. Pola “siapkan tempat dulu” ini sah dan efisien bila ukuran sudah diketahui.
  2. Baris 23-25 (std::back_inserter(genap)): back_inserter adalah iterator output yang setiap kali ditulis memanggil push_back pada container tujuan, sehingga vector tumbuh sesuai jumlah elemen yang lolos predikat. Header yang dibutuhkan: <iterator>.
  3. Baris 24 (copy_if dengan lambda x % 2 == 0): hanya elemen genap yang tersalin, menghasilkan 2 4 6 tanpa perlu menghitung dulu berapa banyak.
  4. Baris 29-30 (std::copy_n(sumber.begin(), 3, ...)): menyalin tepat tiga elemen dari awal sumber. Parameternya jumlah elemen, bukan iterator akhir.
  5. Baris 34-36 (std::move(pesan.begin(), pesan.end(), tujuan.begin())): algoritma std::move memindahkan setiap string dari pesan ke tujuan dengan move assignment, bukan menyalin karakternya satu per satu.
  6. Baris 38: pada implementasi libstdc++ yang dipakai GCC 14, string yang dipindah menjadi kosong. Standar menjamin objek sumber tetap valid — bisa di-assign ulang atau dihancurkan — tetapi isi pastinya ditentukan implementasi.

back_inserter: Tujuan yang Tumbuh Otomatis #

std::back_inserter(c) adalah helper yang menghasilkan std::back_insert_iterator, iterator output khusus yang selalu menulis di akhir container. Karena kategori output iterator memang hanya mendukung tulis dan maju, back_inserter termasuk kategori paling sederhana di jenjang kategori iterator. Ada juga saudaranya std::front_inserter untuk container yang mendukung push_front seperti std::deque dan std::list.

Algoritma std::move vs Utilitas std::move #

Dua hal bernama std::move ini sering tertukar. Utilitas std::move(x) dari <utility> adalah cast: ia menandai satu ekspresi sebagai rvalue agar move constructor atau move assignment yang dipilih — materi lengkapnya ada di Section Move Semantics dan Value Categories. Sedangkan algoritma std::move(first, last, d_first) dari <algorithm> menerima rentang dan memindahkan setiap elemen ke tujuan. Secara internal, algoritma inilah yang memanggil utilitas std::move per elemen, sehingga elemen bertipe mahal-disalin (misalnya std::string panjang atau std::vector) berpindah dengan hanya menukar pointer internalnya.

Dalam praktik, keluarga ini paling sering dipakai untuk menyaring data ke container lain (copy_if), mengambil sebagian data (copy_n), dan mengalihkan kepemilikan isi antar container (std::move). Untuk mengubah isi saat memindah — bukan sekadar menyalin — alat yang tepat adalah std::transform, topik chapter berikutnya.

Rujukan resmi: std::copy, copy_if, copy_n , algoritma std::move , dan std::back_inserter .