Setiap operasi file yang kita tulis sejauh ini diam-diam membawa satu asumsi: semuanya akan berhasil. Kenyataannya, membuka dan membaca file bisa gagal karena banyak alasan — nama file salah, folder belum dibuat, izin baca dicabut, atau disk penuh. Library C++ menyediakan dua gaya untuk melaporkan kegagalan tersebut: fungsi <filesystem> yang melempar exception filesystem_error, dan overload-nya yang menerima std::error_code tanpa melempar apa pun. Chapter ini mengajak kita menguasai keduanya, lalu merangkai pola baca file yang tangguh: memeriksa keberadaan lebih dulu, memeriksa keberhasilan buka, dan memberi pesan yang benar-benar menjelaskan.

Dua Overload untuk Setiap Fungsi #

Konvensi <filesystem> sangat teratur: hampir setiap fungsi punya dua wajah. Versi tanpa parameter tambahan melempar std::filesystem::filesystem_error saat operasi gagal; versi dengan parameter std::error_code& menyimpan status kegagalan ke objek tersebut dan kembali normal. Pemilihan gaya memengaruhi bentuk alur program kita:

AspekOverload exceptionOverload error_code
Saat gagalMelempar filesystem_errorMengisi ec, tidak melempar
Alur suksesLangsung lanjutWajib memeriksa ec
Cocok untukLogika aplikasi, kegagalan jarangAlat baris perintah, kegagalan wajar
Kegagalan terlewatTidak mungkinMungkin, bila ec tidak diperiksa

Program berikut memicu kegagalan yang sama — file_size pada file yang tidak ada — lewat kedua overload:

cpp
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <system_error>

namespace fs = std::filesystem;

int main() {
    std::cout << std::boolalpha;

    // 1. Overload tanpa error_code: kegagalan = exception
    try {
        static_cast<void>(fs::file_size("tidak-ada.txt"));
        std::cout << "Pemanggilan berhasil\n";
    } catch (const fs::filesystem_error& e) {
        std::cout << "Tertangkap exception:\n";
        std::cout << "  e.what() : " << e.what() << '\n';
        std::cout << "  e.code() : " << e.code().value() << " ("
                  << e.code().message() << ")\n";
    }

    // 2. Overload dengan error_code: kegagalan = status, tanpa lemparan
    std::error_code ec;
    std::uintmax_t ukuran = fs::file_size("tidak-ada.txt", ec);
    std::cout << "Overload error_code:\n";
    std::cout << "  ec       : " << static_cast<bool>(ec) << '\n';
    std::cout << "  value    : " << ec.value() << '\n';
    std::cout << "  pesan    : " << ec.message() << '\n';
    std::cout << "  cocok    : "
              << (ec == std::errc::no_such_file_or_directory) << '\n';
    std::cout << "  ukuran   : " << ukuran
              << " (sentinel, periksa ec dulu)\n";

    // 3. Jalur normal: ec tetap bersih bila berhasil
    {
        std::ofstream buat("ada.txt");
        buat << "abcdef\n";
    }
    ec.clear();
    ukuran = fs::file_size("ada.txt", ec);
    if (!ec) {
        std::cout << "ada.txt   : " << ukuran
                  << " byte, ec tetap bersih\n";
    }
    fs::remove("ada.txt");
    return 0;
}

Output yang Dihasilkan:

text
Tertangkap exception:
  e.what() : filesystem error: cannot get file size: No such file or directory [tidak-ada.txt]
  e.code() : 2 (No such file or directory)
Overload error_code:
  ec       : true
  value    : 2
  pesan    : No such file or directory
  cocok    : true
  ukuran   : 18446744073709551615 (sentinel, periksa ec dulu)
ada.txt   : 7 byte, ec tetap bersih
  1. Baris 13-21 (versi exception): file_size pada file yang tidak ada melempar filesystem_error, ditangkap catch. Pesan e.what() merangkai nama operasi, alasan dari sistem operasi, dan path yang bermasalah; teksnya berasal dari implementasi standard library (di sini GCC), jadi kata-katanya bisa sedikit berbeda di compiler lain.
  2. Baris 24-25 (versi error_code): pemanggilan yang sama berakhir tenang — tidak ada lemparan. Statusnya dititipkan ke ec, dan nilai kembalian ukuran tidak boleh dipercaya saat ec terisi.
  3. Baris 27-29 (membaca status): ec bisa diuji dalam konteks bool — true berarti ada kegagalan. ec.value() adalah nomor galat dari sistem operasi (di Linux, 2 untuk file not found) dan ec.message() menerjemahkannya jadi teks.
  4. Baris 30-31 (pencocokan jenis galat): membandingkan ec dengan enumerator std::errc jauh lebih andal daripada mengecek angka secara manual — ec == std::errc::no_such_file_or_directory tetap benar lintas platform, walau nomor angkanya bisa berbeda.
  5. Baris 32-33 (nilai sentinel): bila ec terisi, nilai kembalian berupa sentinel (angka terbesar uintmax_t, hasil dari −1 yang di-cast). Inilah alasan aturan kita: periksa ec dulu, baru pakai nilai kembalian.
  6. Baris 40-45 (jalur normal): setelah ec.clear(), file_size yang berhasil meninggalkan ec kosong — if (!ec) adalah bentuk percabangan paling umum di gaya ini.

Membaca File Secara Tangguh #

Gaya error_code pas untuk urusan file karena kegagalannya memang bagian normal dari kehidupan program — pengguna salah mengetik nama, folder belum dibuat. Pola yang kita rekomendasikan: periksa keberadaan dengan exists(path, ec), buka dengan ifstream, periksa keberhasilan buka, baru baca. Program berikut menguji pola itu pada tiga kondisi: file normal, file yang tidak ada, dan file yang ada tetapi izin bacanya dicabut lewat fs::permissions:

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

namespace fs = std::filesystem;

// Pola tangguh membaca file: setiap mode gagal punya pesan yang jelas.
bool bacaFile(const fs::path& lokasi, std::string& isi) {
    std::error_code ec;
    if (!fs::exists(lokasi, ec)) {
        std::cerr << "Baca gagal: '" << lokasi.string() << "' tidak ada\n";
        return false;
    }
    std::ifstream masuk(lokasi);
    if (!masuk) {
        std::cerr << "Baca gagal: '" << lokasi.string()
                  << "' ada tetapi tidak bisa dibuka\n";
        return false;
    }
    isi.clear();
    std::string baris;
    while (std::getline(masuk, baris)) {
        isi += baris;
        isi += '\n';
    }
    return true;
}

int main() {
    std::cout << std::boolalpha;

    // 1. File yang ada: baca normal
    {
        std::ofstream buat("nilai.txt");
        buat << "Nilai kuis: 88\n";
    }
    std::string isi;
    bool ok = bacaFile("nilai.txt", isi);
    std::cout << "bacaFile(\"nilai.txt\")    : " << ok << '\n';
    std::cout << "isi: " << isi;

    // 2. File yang tidak ada: ditolak sebelum mencoba membuka
    ok = bacaFile("tidak-ada.txt", isi);
    std::cout << "bacaFile(\"tidak-ada.txt\"): " << ok << '\n';

    // 3. File ada tetapi izin baca dicabut: gagal saat membuka
    {
        std::ofstream buat("rahasia.txt");
        buat << "isi terbatas\n";
    }
    fs::permissions("rahasia.txt", fs::perms::owner_read,
                    fs::perm_options::remove);
    ok = bacaFile("rahasia.txt", isi);
    std::cout << "bacaFile(\"rahasia.txt\")  : " << ok << '\n';

    // 4. Rapikan: kembalikan izin lalu hapus file percobaan
    fs::permissions("rahasia.txt", fs::perms::owner_read,
                    fs::perm_options::add);
    fs::remove("rahasia.txt");
    fs::remove("nilai.txt");
    return 0;
}

Output yang Dihasilkan:

text
bacaFile("nilai.txt")    : true
isi: Nilai kuis: 88
bacaFile("tidak-ada.txt"): false
bacaFile("rahasia.txt")  : false

Pesan diagnosanya keluar lewat std::cerr, jadi tidak ikut tercetak di output di atas — di terminal Anda keduanya tampil berdampingan:

text Pesan yang dikirim ke stderr
Baca gagal: 'tidak-ada.txt' tidak ada
Baca gagal: 'rahasia.txt' ada tetapi tidak bisa dibuka
  1. Baris 10-14 (pemeriksaan keberadaan): exists dengan error_code menjawab false untuk file yang tidak ada tanpa melempar exception — persis alasan gaya ini dipilih untuk pola tangguh. Kegagalan ditolak sebelum kita menyentuh stream.
  2. Baris 15-20 (pemeriksaan kedua): tetap ada if (!masuk) setelah konstruksi ifstream. Lapisan ini menangkap kasus yang tidak terjangkau pemeriksaan pertama — izin baca dicabut (kondisi ketiga di atas), file ternyata direktori, atau keberadaannya berubah di antara dua pemeriksaan. Perhatikan pesannya berbeda: “ada tetapi tidak bisa dibuka”, bukan “tidak ada”.
  3. Baris 21-27 (pembacaan): loop getline dari Chapter Membaca File Teks menyusun ulang isi file ke isi, satu baris per iterasi. Bila pembacaan berhenti di tengah karena galat I/O, hasilnya sudah terpotong — pembandingan panjang bisa ditambahkan bila aplikasi Anda butuh ketelitian penuh.
  4. Baris 52-55 (mencabut izin): fs::permissions mengubah mode file seperti perintah chmod; opsi remove melepas bit owner_read. Di Linux, ifstream atas file tanpa izin baca gagal terbuka, dan program tetap hidup — ok bernilai false, bukan exception.
  5. Baris 57-61 (kebersihan): izin dikembalikan sebelum remove. Pemulihan state seperti ini praktik yang layak ditiru: contoh yang gagal sebagian jangan meninggalkan file “rongsok” dengan izin aneh di folder pengguna.

Stream Tidak Melempar Exception #

Satu pembagian peran yang perlu diingat: kelas stream (ifstream, ofstream, fstream) secara bawaan tidak pernah melempar exception saat gagal membuka — mereka menyalakan failbit, persis seperti perilaku cin pada Chapter Input Validation. Karena itu pemeriksaan if (!masuk) setelah konstruksi adalah kebiasaan yang tidak boleh dilepas, apa pun gaya yang dipakai untuk <filesystem>. Kedua dunia ini berdampingan dalam satu program tanpa masalah: <filesystem> untuk inspeksi dengan error_code, stream untuk data dengan pemeriksaan state.

Dengan pola tangguh ini, membaca file sudah layak dipercaya: setiap kegagalan punya pesan yang menjelaskan, dan program tidak pernah melanjutkan dengan data yang tidak sah. Tersisa satu pertanyaan besar yang muncul begitu data mulai serius: bagaimana menyimpan kumpulan objek kita — misalnya vector penuh struct — ke file dan mengembalikannya utuh dijalankan berikutnya? Itulah serialisasi, topik chapter penutup section ini.

Referensi #