std::filesystem (C++17)

Path, iterasi direktori, dan metadata berkas.

Daftar isi

Topik ini membahas pustaka std::filesystem dari C++17 untuk pekerjaan berkas di dalam program C++: menyusun dan membedah path, yaitu alamat berkas atau direktori, menelusuri isi direktori, serta membaca metadata seperti ukuran dan jenis berkas. Contoh yang digunakan berupa tugas arsip cadangan sederhana, yaitu membuat struktur berkas sementara, menyalin datanya, dan membersihkannya kembali. Setelah menyelesaikan topik ini, pembaca dapat menangani operasi berkas lintas platform tanpa memanggil API sistem secara langsung.

Objek path dan komponennya#

Tipe dasarnya adalah std::filesystem::path, sebuah kelas yang merepresentasikan alamat tersebut secara portabel. Kelas ini menyimpan path apa adanya dan memisahkan komponennya sesuai aturan platform saat program berjalan, sehingga kode yang sama bekerja pada Linux maupun Windows. Penyambungan komponen memakai operator /, sedangkan pembedahan memakai fungsi anggota seperti parent_path(), filename(), stem(), dan extension().

Program berikut membedah alamat berkas log sensor suhu tanpa menyentuh disk sama sekali, sehingga bisa dipakai sebagai latihan membaca komponen path.

cpp path.cpp
#include <filesystem>
#include <iostream>

int main() {
    namespace fs = std::filesystem;

    // objek path membangun dan membedah alamat berkas tanpa menyentuh disk
    fs::path sumber = "arsip-cadangan/suhu-2026-01.log";

    std::cout << "Path lengkap : " << sumber.string() << "\n";
    std::cout << "Direktori    : " << sumber.parent_path().string() << "\n";
    std::cout << "Nama berkas  : " << sumber.filename().string() << "\n";
    std::cout << "Nama dasar   : " << sumber.stem().string() << "\n";
    std::cout << "Ekstensi     : " << sumber.extension().string() << "\n";

    // operator / menyambung komponen path dengan pemisah yang tepat
    fs::path rekap = fs::path("arsip-cadangan") / "2026" / "rekap.txt";
    std::cout << "Gabungan     : " << rekap.string() << "\n";
    return 0;
}
text Keluaran
Path lengkap : arsip-cadangan/suhu-2026-01.log
Direktori    : arsip-cadangan
Nama berkas  : suhu-2026-01.log
Nama dasar   : suhu-2026-01
Ekstensi     : .log
Gabungan     : arsip-cadangan/2026/rekap.txt

Perintah berikut mengompilasi dan menjalankan program contoh di terminal.

bash
# kompilasi path.cpp dan simpan biner di /tmp
g++ path.cpp -o /tmp/path-demo
# jalankan program hasil kompilasi
/tmp/path-demo
text Keluaran
Path lengkap : arsip-cadangan/suhu-2026-01.log
Direktori    : arsip-cadangan
Nama berkas  : suhu-2026-01.log
Nama dasar   : suhu-2026-01
Ekstensi     : .log
Gabungan     : arsip-cadangan/2026/rekap.txt

Objek path bekerja murni pada tingkat teks, artinya path boleh menunjuk berkas yang tidak ada tanpa menimbulkan error. Fungsi current_path() membaca direktori kerja program, dan bentuknya yang berparameter mengganti direktori kerja tersebut, sehingga path relatif seperti "arsip-cadangan" selalu diinterpretasikan dari direktori kerja itu. Operator relasional membandingkan path secara leksikografis, dan perbandingan inilah yang dipakai std::sort ketika daftar entri perlu diurutkan pada bagian berikutnya.

Membaca isi direktori#

Kelas std::filesystem::directory_iterator menelusuri entri satu direktori, sedangkan recursive_directory_iterator menelusuri seluruh subdirektori di bawahnya. Setiap entri bertipe directory_entry dan menyediakan path(), is_regular_file(), serta file_size(), sehingga data metadata umum sudah tersedia pada objek entri. Karena urutan kunjungan tidak dijamin standar dan berbeda antarplatform, hasil iterasi perlu dikumpulkan lalu diurutkan sebelum dicetak. Pada contoh berikut, pengurutan dilakukan terhadap seluruh path agar direktori dan berkas tampil dalam urutan yang pasti.

Program berikut membuat struktur arsip sementara di direktori kerja, mengurutkan isi direktori utamanya, menghitung total ukuran seluruh berkas secara rekursif, lalu menghapus strukturnya sampai bersih.

cpp arsip.cpp
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <vector>

namespace fs = std::filesystem;

int main() {
    // 1. struktur berkas sementara dibuat di direktori kerja saat program berjalan
    const fs::path arsip = "arsip-cadangan";
    fs::create_directory(arsip);
    fs::create_directory(arsip / "lama");
    std::ofstream(arsip / "suhu-2026-01.log") << "24.5\n25.1\n23.8\n";
    std::ofstream(arsip / "suhu-2026-02.log") << "25.0\n24.7\n";
    std::ofstream(arsip / "lama" / "suhu-2025-12.log") << "23.9\n";

    // 2. salin isi direktori ke vector lalu urutkan, karena urutan
    //    iterasi tidak dijamin standar
    std::vector<fs::path> entri;
    for (const fs::directory_entry& e : fs::directory_iterator(arsip)) {
        entri.push_back(e.path());
    }
    std::sort(entri.begin(), entri.end());

    for (const fs::path& p : entri) {
        if (fs::is_directory(p)) {
            std::cout << p.filename().string() << "  (direktori)\n";
        } else {
            std::cout << p.filename().string() << "  "
                      << fs::file_size(p) << " byte\n";
        }
    }

    // 3. ukur total seluruh berkas secara rekursif
    unsigned long long total = 0;
    for (const fs::directory_entry& e : fs::recursive_directory_iterator(arsip)) {
        if (e.is_regular_file()) {
            total += e.file_size();
        }
    }
    std::cout << "Total: " << total << " byte\n";

    // 4. struktur sementara dihapus seluruhnya sebelum program berakhir
    fs::remove_all(arsip);
    std::cout << "arsip-cadangan masih ada: "
              << std::boolalpha << fs::exists(arsip) << "\n";
    return 0;
}
text Keluaran
lama  (direktori)
suhu-2026-01.log  15 byte
suhu-2026-02.log  10 byte
Total: 30 byte
arsip-cadangan masih ada: false

Beberapa perilaku pada program di atas layak dicermati. Iterator direktori tidak menyertakan entri khusus . dan .., sehingga daftar yang dihasilkan langsung berisi isi direktori yang sebenarnya. Fungsi create_directory mengembalikan bool yang bernilai true hanya bila direktorinya baru dibuat, dan keberadaan direktori lama tidak dianggap error. Untuk penghapusan bertingkat, remove_all mengembalikan jumlah entri yang berhasil dihapus, sedangkan remove hanya menghapus satu berkas atau satu direktori kosong.

Metadata berkas dan penanganan error#

Fungsi bebas seperti exists(), is_directory(), is_regular_file(), dan file_size() membaca metadata berkas melalui sistem operasi. Bentuk bawaan dari fungsi-fungsi ini melempar std::filesystem::filesystem_error apabila targetnya tidak ada atau tidak dapat dibaca, dan bentuk itu cocok untuk kegagalan yang sungguh tidak normal. Setiap fungsi juga menyediakan bentuk kelebihan beban (overload) yang menerima std::error_code, sehingga kegagalan dikembalikan sebagai nilai tanpa exception sama sekali. Bentuk kedua inilah yang biasa dipilih untuk memeriksa berkas yang memang mungkin tidak ada, misalnya laporan hari sebelumnya pada tugas arsip cadangan.

cpp Contoh: membaca ukuran berkas dengan bentuk tanpa exception
// tidak-dijalankan (fragmen, bukan program utuh)
std::error_code ec;
const auto ukuran = std::filesystem::file_size("arsip-cadangan/rekap.txt", ec);
if (ec) {
    std::cout << "Gagal membaca ukuran: " << ec.message() << "\n";
} else {
    std::cout << "Ukuran: " << ukuran << " byte\n";
}

Pola yang sama berlaku untuk create_directory dan remove_all: kegagalan pembuatan direktori yang sudah ada tidak perlu diperlakukan sebagai error, sehingga program cukup memeriksa nilai kembali atau menangkap error bila relevan.

Apabila bentuk exception dipilih, objek std::filesystem::filesystem_error membawa pesan lengkap melalui what(), beserta path yang bersangkutan melalui path1() dan path2(). Informasi itu membantu saat mendiagnosis tugas arsip, karena pesan error dari sistem operasi saja tidak selalu menyebutkan berkas mana yang gagal. Untuk program kecil yang kegagalannya memang fatal, bentuk exception membuat kode lebih pendek karena pemeriksaan nilai kembali tidak diperlukan.

Selain membaca metadata, pustaka ini menyediakan operasi yang mengubah sistem berkas, yaitu copy_file, rename, dan remove, serta space yang membaca sisa kapasitas partisi. Semua operasi tersebut menerima objek path sebagai targetnya. Nilai kembali file_size() bertipe bilangan bulat tak bertanda dan menyatakan ukuran dalam byte. Pada contoh arsip di atas, tiga berkas log berukuran 15, 10, dan 5 byte menghasilkan total 30 byte, dan angka itu mudah dihitung ulang dari panjang teks yang ditulis ke masing-masing berkas.

Kesalahan umum#

  • Mengandalkan urutan hasil iterasi direktori. Urutan tersebut tidak dijamin standar, sehingga laporan dapat tampil acak di mesin satu dan rapi di mesin lain. Perbaikannya adalah mengurutkan daftar path sebelum digunakan, seperti pada arsip.cpp.
  • Menulis berkas ke dalam direktori yang belum dibuat. std::ofstream tidak otomatis membuat direktori induknya, sehingga penulisan gagal diam-diam. Panggil create_directory terlebih dahulu untuk seluruh tingkat direktori yang dibutuhkan.
  • Memanggil file_size() terhadap path yang belum diperiksa. Bentuk bawaan fungsi ini melempar exception ketika berkasnya tidak ada. Gunakan fungsi exists() sebagai saringan atau pilih bentuk std::error_code agar kegagalan ditangani tanpa exception.

Ringkasan#

  • std::filesystem::path menyusun dan membedah alamat berkas secara portabel, dengan operator / sebagai penyambung komponennya.
  • directory_iterator dan recursive_directory_iterator menelusuri direktori, dan hasilnya wajib diurutkan sendiri karena urutan iterasi tidak dijamin.
  • Metadata dibaca lewat exists, is_regular_file, is_directory, dan file_size, yang masing-masing punya bentuk exception dan bentuk std::error_code.

Referensi#