目次
はじめに
C++17の<filesystem>
ライブラリでファイル操作を行う際、パスが存在しない、アクセス権がない、などの理由でエラーが発生することがあります。これらのエラーに適切に対処しないと、プログラムが予期せずクラッシュする可能性があります。
<filesystem>
ライブラリの多くの関数には、エラーを処理するための2つの方法が用意されています。
- 例外 (
try-catch
):try
ブロックで処理を実行し、エラーが発生したらcatch
ブロックで捕捉する、C++の標準的なエラー処理。 error_code
: 関数の引数としてerror_code
オブジェクトを渡し、エラー情報をそこに格納してもらう。例外を発生させない。
この記事では、これら2つのエラーハンドリング手法について、それぞれの使い方と特徴を解説します。
1. error_code
を引数に取るバージョン
関数の最後の引数として std::error_code&
を受け取るオーバーロードです。エラーが発生しても例外はスローされず、代わりに引数で渡したerror_code
オブジェクトにエラー情報が設定されます。
サンプルコード
#include <iostream>
#include <filesystem>
#include <system_error> // error_code
namespace fs = std::filesystem;
int main() {
// 存在しないパス
fs::path non_existent_path = "/this/path/does/not/exist";
std::error_code ec; // エラー情報を格納するオブジェクト
// 1. error_codeを受け取るバージョンの関数を呼び出す
uintmax_t size = fs::file_size(non_existent_path, ec);
// 2. error_codeオブジェクトを評価して、エラーが発生したかチェック
if (ec) {
std::cout << "エラーコード版: ファイルサイズの取得に失敗しました。" << std::endl;
std::cout << " エラー内容: " << ec.message() << std::endl;
} else {
std::cout << "ファイルサイズ: " << size << std::endl;
}
return 0;
}
解説:
fs::file_size
にec
を渡すことで、error_code
版のオーバーロードが選択されます。if (ec)
はif (ec.value() != 0)
と同じ意味で、エラーが発生したかどうかを簡単にチェックできます。ec.message()
で、人間が読める形式のエラーメッセージを取得できます。
2. 例外を投げるバージョン
引数に error_code
を渡さない、基本的なバージョンです。エラーが発生すると fs::filesystem_error
型の例外をスロー(throw)します。
サンプルコード
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path non_existent_path = "/this/path/does/not/exist";
// 1. tryブロックの中で、例外を投げる可能性のある処理を実行
try {
uintmax_t size = fs::file_size(non_existent_path);
std::cout << "ファイルサイズ: " << size << std::endl;
}
// 2. filesystem_error例外をcatchブロックで捕捉
catch (const fs::filesystem_error& e) {
std::cout << "例外版: ファイルサイズの取得に失敗しました。" << std::endl;
std::cout << " エラー内容: " << e.what() << std::endl;
std::cout << " 対象パス: " << e.path1() << std::endl;
}
return 0;
}
解説:
try { ... }
: エラーが発生する可能性のあるコードを囲みます。catch (const fs::filesystem_error& e) { ... }
:try
ブロック内でfilesystem_error
例外が発生した場合に、このブロックが実行されます。e.what()
: エラーの詳細メッセージを取得します。e.path1()
: エラーに関連する1つ目のパスを取得します(rename
やcopy
の場合はe.path2()
も使えます)。
使い分けのポイント
方法 | メリット | デメリット | 主な用途 |
error_code | ・パフォーマンスが良い(例外処理のオーバーヘッドがない)<br>・エラーを通常のロジックの一部として扱える | ・毎回if (ec) のチェックが必要で、コードが冗長になりがち | ・エラーが発生することが「正常なケース」として想定される場合(例: ファイルの存在チェック) |
例外 | ・エラー処理のコードを、正常系の処理から分離できる<br>・コードがすっきりと読みやすくなる | ・例外処理は比較的コストが高い | ・エラーが発生することが「異常な、想定外のケース」である場合(例: ディスクへの書き込み失敗) |
結論として、エラーが予期される通常のフロー(例: ファイルが存在しないかもしれない)であれば error_code
を、プログラムの続行が困難になるような予期せぬ異常事態であれば例外を使う、という使い分けが一般的です。