【C++17】filesystemライブラリのエラーハンドリング(error_codeと例外)

目次

はじめに

C++17の<filesystem>ライブラリでファイル操作を行う際、パスが存在しない、アクセス権がない、などの理由でエラーが発生することがあります。これらのエラーに適切に対処しないと、プログラムが予期せずクラッシュする可能性があります。

<filesystem>ライブラリの多くの関数には、エラーを処理するための2つの方法が用意されています。

  1. 例外 (try-catch): tryブロックで処理を実行し、エラーが発生したらcatchブロックで捕捉する、C++の標準的なエラー処理。
  2. 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_sizeec を渡すことで、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つ目のパスを取得します(renamecopyの場合はe.path2()も使えます)。

使い分けのポイント

方法メリットデメリット主な用途
error_code・パフォーマンスが良い(例外処理のオーバーヘッドがない)<br>・エラーを通常のロジックの一部として扱える・毎回if (ec)のチェックが必要で、コードが冗長になりがち・エラーが発生することが「正常なケース」として想定される場合(例: ファイルの存在チェック)
例外・エラー処理のコードを、正常系の処理から分離できる<br>・コードがすっきりと読みやすくなる・例外処理は比較的コストが高い・エラーが発生することが「異常な、想定外のケース」である場合(例: ディスクへの書き込み失敗)

結論として、エラーが予期される通常のフロー(例: ファイルが存在しないかもしれない)であれば error_code を、プログラムの続行が困難になるような予期せぬ異常事態であれば例外を使う、という使い分けが一般的です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次