Chapter Filesystem Library memakai path sepintas sebagai string yang disambung dengan /. Padahal std::filesystem::path adalah tipe yang jauh lebih cerdas: ia memahami pemisah platform, bisa dibedah menjadi bagian-bagiannya, dan mengetahui bagian mana nama file dan bagian mana ekstensi. Kemampuan ini penting karena program yang rapi jarang menulis path mentah — ia menyambung potongan path, mengganti nama file di ujungnya, atau menarik ekstensi untuk dipilah. Chapter ini membedah tipe path dari operator penyambung hingga komponen-komponennya, lalu melanjutkan penelusuran direktori dengan recursive_directory_iterator yang menyusuri subfolder sampai ke dalam.

Menyambung Path dengan operator/ #

Operator / pada path bekerja seperti operator penjumlahan string yang sadar platform: ia menyisipkan pemisah yang benar (/ di Linux, \ di Windows) hanya bila diperlukan. Ada juga bentuk +=-nya, yaitu path::operator/= untuk menyambung langsung ke variabel. Dengan dua baris penyambungan, kita membangun path bertingkat tanpa pernah menulis karakter pemisah sendiri — kode yang sama benar di Linux dan Windows. Untuk mewujudkan path bertingkat menjadi folder, library menyediakan create_directories (jamak) yang berperilaku seperti mkdir -p: membuat semua folder perantara yang belum ada, sekaligus folder tujuannya.

Membedah Path Menjadi Komponen #

Program berikut menyusun sebuah path, membedahnya, mewujudkannya menjadi folder dan file sungguhan, lalu menyusuri seluruh pohon yang terbentuk:

cpp
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

namespace fs = std::filesystem;

int main() {
    // 1. Menyusun path dari bagian-bagian
    fs::path dasar = "belajar";
    fs::path folder = dasar / "modul-01";
    fs::path file = folder / "catatan.txt";
    std::cout << "Path lengkap: " << file.string() << '\n';

    // 2. Membedah path menjadi komponen
    std::cout << "parent_path : " << file.parent_path().string() << '\n';
    std::cout << "filename    : " << file.filename().string() << '\n';
    std::cout << "stem        : " << file.stem().string() << '\n';
    std::cout << "extension   : " << file.extension().string() << '\n';
    std::cout << std::boolalpha;
    std::cout << "is_absolute : " << file.is_absolute() << '\n';

    // 3. Wujudkan path menjadi folder dan file nyata
    fs::create_directories(folder);
    {
        std::ofstream catatan(file);
        catatan << "isi catatan\n";
    }
    {
        std::ofstream ringkas(dasar / "modul-01" / "ringkas.md");
        ringkas << "ringkasan singkat\n";
    }

    // 4. Susuri seluruh pohon folder secara rekursif
    std::vector<fs::path> semua;
    for (const fs::directory_entry& entri :
         fs::recursive_directory_iterator(dasar)) {
        semua.push_back(entri.path());
    }
    std::sort(semua.begin(), semua.end());
    std::cout << "Isi 'belajar' rekursif (" << semua.size() << " entri):\n";
    for (const fs::path& p : semua) {
        std::cout << "  " << p.string() << '\n';
    }

    // 5. Bersih-bersih seluruh pohon
    fs::remove_all(dasar);
    std::cout << "Setelah remove_all, 'belajar' ada: "
              << fs::exists(dasar) << '\n';
    return 0;
}

Output yang Dihasilkan:

text
Path lengkap: belajar/modul-01/catatan.txt
parent_path : belajar/modul-01
filename    : catatan.txt
stem        : catatan
extension   : .txt
is_absolute : false
Isi 'belajar' rekursif (3 entri):
  belajar/modul-01
  belajar/modul-01/catatan.txt
  belajar/modul-01/ringkas.md
Setelah remove_all, 'belajar' ada: false
  1. Baris 12-14 (penyambungan): tiga potongan disambung dengan / menghasilkan belajar/modul-01/catatan.txt. Semua pembedahan di baris berikutnya bekerja murni pada string path — folder-nya belum harus ada.
  2. Baris 18-21 (komponen): parent_path() mengembalikan semua sebelum pemisah terakhir, filename() bagian setelahnya, stem() filename tanpa ekstensi, dan extension() mulai titik terakhir. Perhatikan ekstensi menyertakan titiknya, sehingga pencocokan menjadi p.extension() == ".txt".
  3. Baris 23 (is_absolute()): false karena path tidak diawali pemisah. Pemeriksaan ini deterministik — tidak seperti fs::absolute() yang hasilnya bergantung mesin (dibahas di bawah).
  4. Baris 26-34 (mewujudkan): create_directories membuat belajar/modul-01 lengkap dengan foldernya; ofstream menerima path langsung, jadi tidak perlu string() dulu.
  5. Baris 37-42 (iterasi rekursif): recursive_directory_iterator mengunjungi folder, lalu isi folder itu, terus turun sampai habis — termasuk folder belajar/modul-01 itu sendiri sebagai entri. Hasilnya ditampung dan diurutkan agar output stabil, sama seperti aturan pada chapter sebelumnya.
  6. Baris 44-46 (cetak): tiga entri tercetak — satu folder dan dua file. Untuk memilah, directory_entry punya is_directory() yang bisa kita pakai sebagai penyaring di dalam loop.
  7. Baris 49-51 (bersih-bersih): remove_all menghapus seluruh pohon sekaligus dan mengembalikan jumlah entri yang dihapus.

Aturan pemenggalan stem/extension punya dua kasus tepi yang sering mengejutkan. Titik terakhir adalah penentu: arsip.tar.gz memiliki stem arsip.tar dan extension .gz — bukan “semua setelah titik pertama”. Dan nama yang diawali titik tanpa titik lain (gaya dotfile seperti .gitignore) diperlakukan sebagai stem utuh tanpa extension.

absolute, relative, dan current_path #

Path tanpa pemisah di depan disebut relative path: lokasinya dihitung dari current working directory saat program berjalan. Lawannya, absolute path, dimulai dari akar sistem berkas (/ di Linux, misalnya C:\ di Windows) dan selalu menunjuk lokasi yang sama ke mana pun program dijalankan. Dua fungsi yang menghubungkan keduanya: fs::current_path() mengembalikan direktori kerja saat ini sebagai path, dan fs::absolute(p) menyatukan current_path() dengan p menjadi path absolut.

Kita sengaja tidak mencetak hasil fs::absolute() di contoh terverifikasi: nilainya memuat lokasi program dijalankan, yang berbeda antara mesin dan antar direktori — persis jenis output yang harus dihindari. Pola penggunaannya tetap bermanfaat, misalnya untuk menampilkan ke pengguna di mana hasil disimpan:

cpp Ilustrasi (keluaran bergantung tempat program dijalankan)
fs::path hasil = "arsip/laporan.txt";
std::cout << "Hasil disimpan di: "
          << fs::absolute(hasil).string() << '\n';
// di mesin dengan cwd /home/dewi/app tercetak:
// Hasil disimpan di: /home/dewi/app/arsip/laporan.txt

string() dan Keluar dari Dunia path #

path bukan std::string, dan compiler tidak mengonversinya secara diam-diam. Saat fungsi menuntut std::string — seperti parameter kita sendiri atau API pihak ketiga — panggil p.string(). Kebalikannya juga sah: konstruktor path menerima std::string dan string literal, itulah yang kita lakukan setiap kali menulis fs::path p = "belajar";. Ada juga p.native() yang mengembalikan representasi sesuai platform (wchar_t-based di Windows); di Linux hasilnya identik dengan string(), sehingga untuk latihan lintas-platform sederhana, string() sudah memadai.

Satu catatan kecil tentang bentuk: path menyimpan string apa adanya tanpa menormalkan — fs::path("a//b/./c").string() tetap tercetak persis begitu, pemisah ganda dan semua. Normalisasi ke bentuk kanonik (menyelesaikan .., symlinks, dan bentuk absolut) ditangani fungsi terpisah, fs::canonical, yang mengharuskan target benar-benar ada.

Dengan path yang kini dikuasai penuh — menyambung, membedah, mengubah ke string, dan menyusuri pohon folder secara rekursif — peralatan sistem berkas kita praktis lengkap. Yang belum kita siapkan adalah rencana cadangan: apa yang terjadi ketika path yang diberikan tidak ada, izinnya kurang, atau file yang mau dibaca ternyata tidak terbuka? Chapter berikutnya membahas persis itu — penanganan galat untuk seluruh operasi file.

Referensi #