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:
| Aspek | Overload exception | Overload error_code |
|---|---|---|
| Saat gagal | Melempar filesystem_error | Mengisi ec, tidak melempar |
| Alur sukses | Langsung lanjut | Wajib memeriksa ec |
| Cocok untuk | Logika aplikasi, kegagalan jarang | Alat baris perintah, kegagalan wajar |
| Kegagalan terlewat | Tidak mungkin | Mungkin, bila ec tidak diperiksa |
Program berikut memicu kegagalan yang sama — file_size pada file yang tidak ada — lewat kedua overload:
#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:
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- Baris 13-21 (versi exception):
file_sizepada file yang tidak ada melemparfilesystem_error, ditangkapcatch. Pesane.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. - Baris 24-25 (versi
error_code): pemanggilan yang sama berakhir tenang — tidak ada lemparan. Statusnya dititipkan keec, dan nilai kembalianukurantidak boleh dipercaya saatecterisi. - Baris 27-29 (membaca status):
ecbisa diuji dalam konteksbool—trueberarti ada kegagalan.ec.value()adalah nomor galat dari sistem operasi (di Linux,2untuk file not found) danec.message()menerjemahkannya jadi teks. - Baris 30-31 (pencocokan jenis galat): membandingkan
ecdengan enumeratorstd::errcjauh lebih andal daripada mengecek angka secara manual —ec == std::errc::no_such_file_or_directorytetap benar lintas platform, walau nomor angkanya bisa berbeda. - Baris 32-33 (nilai sentinel): bila
ecterisi, nilai kembalian berupa sentinel (angka terbesaruintmax_t, hasil dari −1 yang di-cast). Inilah alasan aturan kita: periksaecdulu, baru pakai nilai kembalian. - Baris 40-45 (jalur normal): setelah
ec.clear(),file_sizeyang berhasil meninggalkaneckosong —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:
#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:
bacaFile("nilai.txt") : true
isi: Nilai kuis: 88
bacaFile("tidak-ada.txt"): false
bacaFile("rahasia.txt") : falsePesan diagnosanya keluar lewat std::cerr, jadi tidak ikut tercetak di output di atas — di terminal Anda keduanya tampil berdampingan:
Baca gagal: 'tidak-ada.txt' tidak ada
Baca gagal: 'rahasia.txt' ada tetapi tidak bisa dibuka- Baris 10-14 (pemeriksaan keberadaan):
existsdenganerror_codemenjawabfalseuntuk file yang tidak ada tanpa melempar exception — persis alasan gaya ini dipilih untuk pola tangguh. Kegagalan ditolak sebelum kita menyentuh stream. - Baris 15-20 (pemeriksaan kedua): tetap ada
if (!masuk)setelah konstruksiifstream. 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”. - Baris 21-27 (pembacaan): loop
getlinedari Chapter Membaca File Teks menyusun ulang isi file keisi, 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. - Baris 52-55 (mencabut izin):
fs::permissionsmengubah mode file seperti perintahchmod; opsiremovemelepas bitowner_read. Di Linux,ifstreamatas file tanpa izin baca gagal terbuka, dan program tetap hidup —okbernilaifalse, bukan exception. - 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.