C++でのファイルとディレクトリ操作をstd::filesystemで徹底解説

C++17から標準ライブラリに追加されたstd::filesystemは、ファイルやディレクトリの操作を簡素化し、効率的に行うための強力なツールです。本記事では、ファイルやディレクトリの作成、削除、存在確認、コピー、移動など、std::filesystemを使った基本的な操作方法を徹底的に解説します。これにより、C++プログラマーが日常的に行うファイル操作がより直感的に、かつ効率的に行えるようになります。

目次
  1. std::filesystemの基本概念と利点
    1. std::filesystemの利点
    2. 基本的な使用例
  2. ファイルの作成と削除
    1. ファイルの作成
    2. ファイルの削除
  3. ディレクトリの作成と削除
    1. ディレクトリの作成
    2. ディレクトリの削除
  4. ファイルとディレクトリの存在確認
    1. ファイルの存在確認
    2. ディレクトリの存在確認
    3. 詳細な存在確認
  5. ファイルとディレクトリのコピーと移動
    1. ファイルのコピー
    2. ディレクトリのコピー
    3. ファイルの移動
    4. ディレクトリの移動
  6. ファイルサイズの取得
    1. ファイルサイズの取得方法
    2. 詳細なエラーハンドリング
    3. ディレクトリのサイズ計算
  7. ディレクトリ内のファイル一覧の取得
    1. ディレクトリ内のファイル一覧の取得方法
    2. 再帰的なファイル一覧の取得
    3. 特定のファイルのみをリストアップ
  8. パス操作と正規化
    1. パスの結合
    2. パスの正規化
    3. 相対パスと絶対パスの変換
    4. パスの親ディレクトリの取得
  9. 応用例:再帰的なディレクトリ操作
    1. ディレクトリ内のすべてのファイルを削除
    2. ディレクトリサイズの再帰的な計算
    3. 特定のファイルを再帰的に検索
  10. エラーハンドリング
    1. 基本的なエラーハンドリング
    2. ファイルのコピー時のエラーハンドリング
    3. ディレクトリ操作時のエラーハンドリング
    4. エラーメッセージのカスタマイズ
  11. 演習問題と解答例
    1. 演習問題1: ファイルの存在確認とコピー
    2. 演習問題2: ディレクトリ内のファイル一覧を取得
    3. 演習問題3: ディレクトリ内のファイルサイズの合計を計算
    4. 演習問題4: ファイルの移動とリネーム
  12. まとめ

std::filesystemの基本概念と利点

std::filesystemは、C++17で追加された標準ライブラリの一部で、ファイルシステムの操作を簡素化するための機能を提供します。従来のC++では、ファイル操作に複雑なコードが必要でしたが、std::filesystemを使うことで、これらの操作が直感的に行えるようになります。

std::filesystemの利点

std::filesystemの利点は以下の通りです:

1. 標準ライブラリとしての安定性と移植性

標準ライブラリに含まれているため、異なるプラットフォーム間での移植性が高く、安定しています。

2. 豊富な機能セット

ファイルの作成、削除、コピー、移動、存在確認、パス操作など、ファイルシステムに関するあらゆる操作をサポートしています。

3. 直感的なインターフェース

std::filesystemのAPIは直感的で分かりやすく、コードの可読性が向上します。

基本的な使用例

以下に、std::filesystemの基本的な使用例を示します:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "example.txt";

    // ファイルの存在確認
    if (fs::exists(filePath)) {
        std::cout << "File exists." << std::endl;
    } else {
        std::cout << "File does not exist." << std::endl;
    }

    return 0;
}

この例では、ファイルの存在確認を行っています。std::filesystemを用いることで、簡潔に記述できることがわかります。次に、具体的な操作方法について詳しく見ていきましょう。

ファイルの作成と削除

std::filesystemを使用すると、ファイルの作成と削除が非常に簡単になります。このセクションでは、これらの基本操作を具体的に説明します。

ファイルの作成

std::filesystemを使ったファイルの作成は簡単です。以下のコードは、新しいファイルを作成する例です:

#include <iostream>
#include <filesystem>
#include <fstream>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "newfile.txt";

    // ファイルの作成
    std::ofstream file(filePath);
    if (file) {
        std::cout << "File created successfully." << std::endl;
    } else {
        std::cout << "Failed to create file." << std::endl;
    }

    return 0;
}

このコードでは、std::ofstreamを使って新しいファイルを作成しています。ファイルの作成に成功した場合、「File created successfully.」と表示されます。

ファイルの削除

次に、ファイルの削除方法を説明します。std::filesystemのremove関数を使うと、簡単にファイルを削除できます:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "newfile.txt";

    // ファイルの削除
    if (fs::remove(filePath)) {
        std::cout << "File deleted successfully." << std::endl;
    } else {
        std::cout << "Failed to delete file." << std::endl;
    }

    return 0;
}

このコードでは、指定したファイルが存在する場合、それを削除し、「File deleted successfully.」と表示されます。ファイルが存在しない場合や削除に失敗した場合には、「Failed to delete file.」と表示されます。

ファイルの作成と削除は、ファイル操作の基本中の基本です。次のセクションでは、ディレクトリの作成と削除について解説します。

ディレクトリの作成と削除

std::filesystemを使用することで、ディレクトリの作成と削除も簡単に行えます。このセクションでは、これらの基本操作を具体的に説明します。

ディレクトリの作成

ディレクトリを作成するには、std::filesystemのcreate_directory関数を使用します。以下のコードは、新しいディレクトリを作成する例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path dirPath = "new_directory";

    // ディレクトリの作成
    if (fs::create_directory(dirPath)) {
        std::cout << "Directory created successfully." << std::endl;
    } else {
        std::cout << "Failed to create directory." << std::endl;
    }

    return 0;
}

このコードでは、指定されたパスに新しいディレクトリを作成しています。作成に成功した場合、「Directory created successfully.」と表示されます。

ディレクトリの削除

ディレクトリを削除するには、std::filesystemのremoveまたはremove_all関数を使用します。remove関数はディレクトリが空の場合のみ削除し、remove_all関数はディレクトリ内の全てのファイルとサブディレクトリを再帰的に削除します。以下のコードは、ディレクトリを削除する例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path dirPath = "new_directory";

    // ディレクトリの削除
    if (fs::remove_all(dirPath)) {
        std::cout << "Directory and its contents deleted successfully." << std::endl;
    } else {
        std::cout << "Failed to delete directory or directory does not exist." << std::endl;
    }

    return 0;
}

このコードでは、指定されたディレクトリとその中の全ての内容を削除します。削除に成功した場合、「Directory and its contents deleted successfully.」と表示されます。

ディレクトリの作成と削除は、ファイル操作と同様に基本的な操作です。次のセクションでは、ファイルやディレクトリの存在確認方法について解説します。

ファイルとディレクトリの存在確認

ファイルやディレクトリの操作を行う前に、それらが存在するかどうかを確認することが重要です。std::filesystemを使用すると、これらの存在確認も簡単に行えます。

ファイルの存在確認

ファイルの存在確認には、std::filesystemのexists関数を使用します。以下のコードは、ファイルの存在を確認する例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "example.txt";

    // ファイルの存在確認
    if (fs::exists(filePath)) {
        std::cout << "File exists." << std::endl;
    } else {
        std::cout << "File does not exist." << std::endl;
    }

    return 0;
}

このコードでは、指定されたファイルが存在するかどうかを確認し、存在する場合には「File exists.」と表示されます。

ディレクトリの存在確認

ディレクトリの存在確認も同様に、exists関数を使用します。以下のコードは、ディレクトリの存在を確認する例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path dirPath = "example_directory";

    // ディレクトリの存在確認
    if (fs::exists(dirPath) && fs::is_directory(dirPath)) {
        std::cout << "Directory exists." << std::endl;
    } else {
        std::cout << "Directory does not exist." << std::endl;
    }

    return 0;
}

このコードでは、指定されたパスがディレクトリかどうかも確認しています。存在し、かつディレクトリである場合には「Directory exists.」と表示されます。

詳細な存在確認

場合によっては、パスがファイルかディレクトリかを区別する必要があります。そのために、is_regular_file関数やis_directory関数を使用します。以下に、その例を示します:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path path = "example_path";

    // ファイルかディレクトリかを確認
    if (fs::exists(path)) {
        if (fs::is_regular_file(path)) {
            std::cout << "It is a file." << std::endl;
        } else if (fs::is_directory(path)) {
            std::cout << "It is a directory." << std::endl;
        } else {
            std::cout << "It exists but is neither a file nor a directory." << std::endl;
        }
    } else {
        std::cout << "Path does not exist." << std::endl;
    }

    return 0;
}

このコードでは、指定されたパスがファイルかディレクトリか、またはそれ以外の何かかを確認しています。これにより、より詳細な存在確認が可能になります。

次のセクションでは、ファイルとディレクトリのコピーと移動について解説します。

ファイルとディレクトリのコピーと移動

std::filesystemを使用すると、ファイルやディレクトリのコピーや移動が簡単に行えます。このセクションでは、これらの操作方法について具体的に説明します。

ファイルのコピー

ファイルをコピーするには、std::filesystemのcopy関数を使用します。以下のコードは、ファイルをコピーする例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path source = "source.txt";
    fs::path destination = "destination.txt";

    // ファイルのコピー
    try {
        fs::copy(source, destination, fs::copy_options::overwrite_existing);
        std::cout << "File copied successfully." << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to copy file: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、source.txtをdestination.txtにコピーしています。copy_options::overwrite_existingオプションを指定することで、既存のファイルがあっても上書きします。

ディレクトリのコピー

ディレクトリをコピーする場合も同様に、copy関数を使用しますが、再帰的にコピーするためにcopy_options::recursiveオプションを指定します:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path source = "source_directory";
    fs::path destination = "destination_directory";

    // ディレクトリのコピー
    try {
        fs::copy(source, destination, fs::copy_options::recursive);
        std::cout << "Directory copied successfully." << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to copy directory: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、source_directoryをdestination_directoryに再帰的にコピーしています。

ファイルの移動

ファイルを移動するには、std::filesystemのrename関数を使用します。以下のコードは、ファイルを移動する例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path source = "source.txt";
    fs::path destination = "moved.txt";

    // ファイルの移動
    try {
        fs::rename(source, destination);
        std::cout << "File moved successfully." << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to move file: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、source.txtをmoved.txtにリネーム(移動)しています。

ディレクトリの移動

ディレクトリを移動する場合も、rename関数を使用します。以下のコードは、ディレクトリを移動する例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path source = "source_directory";
    fs::path destination = "moved_directory";

    // ディレクトリの移動
    try {
        fs::rename(source, destination);
        std::cout << "Directory moved successfully." << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to move directory: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、source_directoryをmoved_directoryにリネーム(移動)しています。

ファイルとディレクトリのコピーおよび移動は、日常的なファイル操作でよく使用されるため、これらの方法を理解しておくことが重要です。次のセクションでは、ファイルサイズの取得について解説します。

ファイルサイズの取得

ファイルサイズを取得することは、ファイル操作において重要な情報を得るための基本的な操作の一つです。std::filesystemを使用すると、簡単にファイルサイズを取得できます。

ファイルサイズの取得方法

std::filesystemのfile_size関数を使用して、特定のファイルのサイズを取得することができます。以下のコードは、ファイルサイズを取得する例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "example.txt";

    try {
        // ファイルサイズの取得
        auto size = fs::file_size(filePath);
        std::cout << "File size: " << size << " bytes" << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to get file size: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、example.txtというファイルのサイズを取得し、バイト単位で表示しています。ファイルが存在しない場合やアクセス権がない場合は、例外が発生します。

詳細なエラーハンドリング

ファイルサイズを取得する際に、例外を適切に処理することは重要です。上記のコードでは、filesystem_error例外をキャッチしてエラーメッセージを表示しています。これにより、予期しないエラーに対処することができます。

ディレクトリのサイズ計算

ディレクトリ全体のサイズを取得するには、ディレクトリ内のすべてのファイルサイズを再帰的に合計する必要があります。以下のコードは、その方法を示しています:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

// ディレクトリサイズの計算
uintmax_t calculate_directory_size(const fs::path& dirPath) {
    uintmax_t size = 0;
    for (const auto& entry : fs::recursive_directory_iterator(dirPath)) {
        if (fs::is_regular_file(entry.path())) {
            size += fs::file_size(entry.path());
        }
    }
    return size;
}

int main() {
    fs::path dirPath = "example_directory";

    try {
        // ディレクトリサイズの取得
        auto size = calculate_directory_size(dirPath);
        std::cout << "Directory size: " << size << " bytes" << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to get directory size: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、example_directory内のすべてのファイルのサイズを再帰的に合計し、そのサイズをバイト単位で表示しています。

ファイルサイズの取得は、ファイル管理やディスク使用量の監視など、多くの場面で役立ちます。次のセクションでは、ディレクトリ内のファイル一覧の取得について解説します。

ディレクトリ内のファイル一覧の取得

ディレクトリ内のファイル一覧を取得することは、ファイル操作において頻繁に必要となる操作の一つです。std::filesystemを使用すると、簡単にディレクトリ内のファイル一覧を取得できます。

ディレクトリ内のファイル一覧の取得方法

std::filesystemのdirectory_iteratorを使用して、指定したディレクトリ内のファイルやサブディレクトリの一覧を取得できます。以下のコードは、ディレクトリ内のファイル一覧を取得する例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path dirPath = "example_directory";

    try {
        // ディレクトリ内のファイル一覧の取得
        for (const auto& entry : fs::directory_iterator(dirPath)) {
            std::cout << entry.path() << std::endl;
        }
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to list directory contents: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、example_directory内のすべてのファイルとサブディレクトリのパスを表示しています。

再帰的なファイル一覧の取得

ディレクトリ内のすべてのファイルとサブディレクトリ、およびそれらの中のファイルも含めて一覧を取得したい場合、recursive_directory_iteratorを使用します。以下のコードは、その例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path dirPath = "example_directory";

    try {
        // 再帰的なディレクトリ内のファイル一覧の取得
        for (const auto& entry : fs::recursive_directory_iterator(dirPath)) {
            std::cout << entry.path() << std::endl;
        }
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to list directory contents recursively: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、example_directoryおよびそのサブディレクトリ内のすべてのファイルとディレクトリのパスを再帰的に表示しています。

特定のファイルのみをリストアップ

特定の拡張子を持つファイルのみをリストアップする場合、条件付きで表示することができます。以下に、その例を示します:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path dirPath = "example_directory";

    try {
        // 特定の拡張子を持つファイルのみをリストアップ
        for (const auto& entry : fs::directory_iterator(dirPath)) {
            if (entry.path().extension() == ".txt") {
                std::cout << entry.path() << std::endl;
            }
        }
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to list specific files in directory: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、example_directory内のすべての.txtファイルのパスを表示しています。

ディレクトリ内のファイル一覧の取得は、ファイル管理やデータ処理の準備など、多くの場面で役立ちます。次のセクションでは、パス操作と正規化について解説します。

パス操作と正規化

パス操作は、ファイルやディレクトリを扱う際に頻繁に行う必要があります。std::filesystemは、パス操作を簡単に行うための豊富な機能を提供しています。このセクションでは、パスの操作や正規化について解説します。

パスの結合

std::filesystemでは、/演算子を使用してパスを結合することができます。以下のコードは、パスを結合する例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path dirPath = "example_directory";
    fs::path filePath = "file.txt";

    // パスの結合
    fs::path fullPath = dirPath / filePath;
    std::cout << "Full path: " << fullPath << std::endl;

    return 0;
}

このコードでは、example_directoryとfile.txtを結合して、完全なパスを生成しています。

パスの正規化

パスの正規化とは、冗長な部分を取り除き、一貫した形式に整えることです。std::filesystemのcanonical関数を使用してパスを正規化できます:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path path = "example_directory/./subdir/../file.txt";

    try {
        // パスの正規化
        fs::path normalizedPath = fs::canonical(path);
        std::cout << "Canonical path: " << normalizedPath << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to canonicalize path: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、example_directory/./subdir/../file.txtというパスを正規化して、冗長な部分を取り除いたパスを生成しています。

相対パスと絶対パスの変換

std::filesystemでは、相対パスを絶対パスに変換することができます。absolute関数を使用します:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path relativePath = "example_directory/file.txt";

    // 相対パスを絶対パスに変換
    fs::path absolutePath = fs::absolute(relativePath);
    std::cout << "Absolute path: " << absolutePath << std::endl;

    return 0;
}

このコードでは、相対パスexample_directory/file.txtを絶対パスに変換しています。

パスの親ディレクトリの取得

パスの親ディレクトリを取得するには、parent_path関数を使用します:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path fullPath = "example_directory/subdir/file.txt";

    // 親ディレクトリの取得
    fs::path parentDir = fullPath.parent_path();
    std::cout << "Parent directory: " << parentDir << std::endl;

    return 0;
}

このコードでは、example_directory/subdir/file.txtの親ディレクトリを取得しています。

パス操作と正規化は、ファイルシステムを効率的に操作するための重要な技術です。次のセクションでは、再帰的なディレクトリ操作の応用例について解説します。

応用例:再帰的なディレクトリ操作

再帰的なディレクトリ操作は、ディレクトリ内のすべてのファイルとサブディレクトリを対象に処理を行う際に非常に便利です。std::filesystemを使用すると、これらの操作も簡単に行うことができます。このセクションでは、再帰的なディレクトリ操作の応用例を紹介します。

ディレクトリ内のすべてのファイルを削除

特定のディレクトリ内のすべてのファイルを再帰的に削除する例を示します:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

void remove_all_files(const fs::path& dirPath) {
    for (const auto& entry : fs::recursive_directory_iterator(dirPath)) {
        if (fs::is_regular_file(entry.path())) {
            fs::remove(entry.path());
        }
    }
}

int main() {
    fs::path dirPath = "example_directory";

    try {
        // ディレクトリ内のすべてのファイルを削除
        remove_all_files(dirPath);
        std::cout << "All files in the directory have been deleted." << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to delete files: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、example_directory内のすべてのファイルを再帰的に削除しています。サブディレクトリ内のファイルも対象になります。

ディレクトリサイズの再帰的な計算

ディレクトリ内のすべてのファイルサイズを再帰的に合計して、ディレクトリ全体のサイズを計算する例を示します:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

// ディレクトリサイズの計算
uintmax_t calculate_directory_size(const fs::path& dirPath) {
    uintmax_t size = 0;
    for (const auto& entry : fs::recursive_directory_iterator(dirPath)) {
        if (fs::is_regular_file(entry.path())) {
            size += fs::file_size(entry.path());
        }
    }
    return size;
}

int main() {
    fs::path dirPath = "example_directory";

    try {
        // ディレクトリサイズの取得
        auto size = calculate_directory_size(dirPath);
        std::cout << "Directory size: " << size << " bytes" << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to calculate directory size: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、example_directory内のすべてのファイルサイズを再帰的に合計し、そのサイズをバイト単位で表示しています。

特定のファイルを再帰的に検索

特定のファイル名を持つファイルをディレクトリ内で再帰的に検索する例を示します:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

void find_file(const fs::path& dirPath, const std::string& fileName) {
    for (const auto& entry : fs::recursive_directory_iterator(dirPath)) {
        if (entry.path().filename() == fileName) {
            std::cout << "File found: " << entry.path() << std::endl;
        }
    }
}

int main() {
    fs::path dirPath = "example_directory";
    std::string fileName = "target_file.txt";

    try {
        // 特定のファイルを再帰的に検索
        find_file(dirPath, fileName);
    } catch (const fs::filesystem_error& e) {
        std::cout << "Failed to search for file: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、example_directory内でtarget_file.txtという名前のファイルを再帰的に検索し、見つかった場合にそのパスを表示します。

再帰的なディレクトリ操作は、複雑なファイルシステムの操作を効率的に行うための強力な手法です。次のセクションでは、std::filesystemを使った操作でのエラーハンドリングについて解説します。

エラーハンドリング

ファイルやディレクトリの操作中にエラーが発生することは避けられません。std::filesystemを使用するときには、これらのエラーを適切に処理することが重要です。このセクションでは、エラーハンドリングの方法について解説します。

基本的なエラーハンドリング

std::filesystemの関数は、多くの場合、例外をスローします。これらの例外をキャッチして処理することで、プログラムが予期しないクラッシュを避けることができます。以下のコードは、ファイルの存在確認時に発生する可能性のあるエラーを処理する例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "example.txt";

    try {
        // ファイルの存在確認
        if (fs::exists(filePath)) {
            std::cout << "File exists." << std::endl;
        } else {
            std::cout << "File does not exist." << std::endl;
        }
    } catch (const fs::filesystem_error& e) {
        std::cout << "Error checking file existence: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、filesystem_error例外をキャッチして、エラーメッセージを表示しています。

ファイルのコピー時のエラーハンドリング

ファイルをコピーする際には、アクセス権の問題やファイルが存在しない場合など、さまざまなエラーが発生する可能性があります。以下のコードは、ファイルコピー時のエラーハンドリングの例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path source = "source.txt";
    fs::path destination = "destination.txt";

    try {
        // ファイルのコピー
        fs::copy(source, destination, fs::copy_options::overwrite_existing);
        std::cout << "File copied successfully." << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cout << "Error copying file: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、ファイルコピー中に発生する可能性のあるエラーをキャッチして、エラーメッセージを表示しています。

ディレクトリ操作時のエラーハンドリング

ディレクトリ操作も同様にエラーが発生する可能性があります。以下のコードは、ディレクトリ削除時のエラーハンドリングの例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path dirPath = "example_directory";

    try {
        // ディレクトリの削除
        if (fs::remove_all(dirPath)) {
            std::cout << "Directory and its contents deleted successfully." << std::endl;
        } else {
            std::cout << "Failed to delete directory or directory does not exist." << std::endl;
        }
    } catch (const fs::filesystem_error& e) {
        std::cout << "Error deleting directory: " << e.what() << std::endl;
    }

    return 0;
}

このコードでは、ディレクトリ削除中に発生する可能性のあるエラーをキャッチして、エラーメッセージを表示しています。

エラーメッセージのカスタマイズ

例外をキャッチした際に、より詳細なエラーメッセージを提供することができます。以下のコードは、エラーメッセージをカスタマイズする例です:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "example.txt";

    try {
        // ファイルの存在確認
        if (fs::exists(filePath)) {
            std::cout << "File exists." << std::endl;
        } else {
            std::cout << "File does not exist." << std::endl;
        }
    } catch (const fs::filesystem_error& e) {
        std::cout << "Error checking file existence: " << e.what() << std::endl;
        std::cout << "Path: " << e.path1() << std::endl;
        if (e.path2() != "") {
            std::cout << "Target path: " << e.path2() << std::endl;
        }
    }

    return 0;
}

このコードでは、例外オブジェクトから追加情報を取得し、詳細なエラーメッセージを提供しています。

エラーハンドリングは、堅牢なプログラムを作成するために不可欠な要素です。次のセクションでは、学習を深めるための演習問題とその解答例を提供します。

演習問題と解答例

std::filesystemの理解を深めるために、いくつかの演習問題を用意しました。これらの問題を解くことで、実際のプログラムでstd::filesystemをどのように活用するかを学ぶことができます。

演習問題1: ファイルの存在確認とコピー

指定されたファイルが存在する場合に、そのファイルを別のディレクトリにコピーするプログラムを作成してください。

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path source = "example.txt";
    fs::path destinationDir = "backup_directory";
    fs::path destination = destinationDir / source.filename();

    try {
        // ディレクトリが存在しない場合は作成
        if (!fs::exists(destinationDir)) {
            fs::create_directory(destinationDir);
        }

        // ファイルの存在確認とコピー
        if (fs::exists(source)) {
            fs::copy(source, destination, fs::copy_options::overwrite_existing);
            std::cout << "File copied to backup directory." << std::endl;
        } else {
            std::cout << "Source file does not exist." << std::endl;
        }
    } catch (const fs::filesystem_error& e) {
        std::cout << "Error: " << e.what() << std::endl;
    }

    return 0;
}

このプログラムでは、example.txtが存在する場合に、backup_directoryにファイルをコピーします。backup_directoryが存在しない場合は、新しく作成します。

演習問題2: ディレクトリ内のファイル一覧を取得

指定されたディレクトリ内のすべてのファイルとサブディレクトリの一覧を取得し、それぞれのパスを表示するプログラムを作成してください。

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path dirPath = "example_directory";

    try {
        // ディレクトリ内のファイル一覧の取得
        for (const auto& entry : fs::directory_iterator(dirPath)) {
            std::cout << entry.path() << std::endl;
        }
    } catch (const fs::filesystem_error& e) {
        std::cout << "Error: " << e.what() << std::endl;
    }

    return 0;
}

このプログラムでは、example_directory内のすべてのファイルとサブディレクトリのパスを表示します。

演習問題3: ディレクトリ内のファイルサイズの合計を計算

指定されたディレクトリ内のすべてのファイルサイズを再帰的に合計し、ディレクトリ全体のサイズをバイト単位で表示するプログラムを作成してください。

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

// ディレクトリサイズの計算
uintmax_t calculate_directory_size(const fs::path& dirPath) {
    uintmax_t size = 0;
    for (const auto& entry : fs::recursive_directory_iterator(dirPath)) {
        if (fs::is_regular_file(entry.path())) {
            size += fs::file_size(entry.path());
        }
    }
    return size;
}

int main() {
    fs::path dirPath = "example_directory";

    try {
        // ディレクトリサイズの取得
        auto size = calculate_directory_size(dirPath);
        std::cout << "Directory size: " << size << " bytes" << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cout << "Error: " << e.what() << std::endl;
    }

    return 0;
}

このプログラムでは、example_directory内のすべてのファイルサイズを再帰的に合計し、そのサイズをバイト単位で表示します。

演習問題4: ファイルの移動とリネーム

指定されたファイルを別のディレクトリに移動し、新しい名前にリネームするプログラムを作成してください。

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path source = "example.txt";
    fs::path destinationDir = "new_directory";
    fs::path destination = destinationDir / "renamed_file.txt";

    try {
        // ディレクトリが存在しない場合は作成
        if (!fs::exists(destinationDir)) {
            fs::create_directory(destinationDir);
        }

        // ファイルの移動とリネーム
        if (fs::exists(source)) {
            fs::rename(source, destination);
            std::cout << "File moved and renamed successfully." << std::endl;
        } else {
            std::cout << "Source file does not exist." << std::endl;
        }
    } catch (const fs::filesystem_error& e) {
        std::cout << "Error: " << e.what() << std::endl;
    }

    return 0;
}

このプログラムでは、example.txtをnew_directoryに移動し、renamed_file.txtとしてリネームしています。new_directoryが存在しない場合は、新しく作成します。

これらの演習問題を通じて、std::filesystemの基本操作に加えて、実際のシナリオでの応用力を養うことができます。次のセクションでは、本記事のまとめを行います。

まとめ

本記事では、C++のstd::filesystemを用いたファイルおよびディレクトリの操作方法について、基本から応用まで詳しく解説しました。std::filesystemを使用することで、従来の複雑なファイル操作が簡素化され、より直感的かつ効率的に行えるようになります。ファイルの作成・削除、ディレクトリの作成・削除、ファイルの存在確認、コピー・移動、ファイルサイズの取得、ディレクトリ内のファイル一覧の取得、パスの操作と正規化、再帰的なディレクトリ操作、エラーハンドリングなど、幅広い操作方法を学びました。これらの知識を活用し、実際のプロジェクトでstd::filesystemを活用してみてください。

コメント

コメントする

目次
  1. std::filesystemの基本概念と利点
    1. std::filesystemの利点
    2. 基本的な使用例
  2. ファイルの作成と削除
    1. ファイルの作成
    2. ファイルの削除
  3. ディレクトリの作成と削除
    1. ディレクトリの作成
    2. ディレクトリの削除
  4. ファイルとディレクトリの存在確認
    1. ファイルの存在確認
    2. ディレクトリの存在確認
    3. 詳細な存在確認
  5. ファイルとディレクトリのコピーと移動
    1. ファイルのコピー
    2. ディレクトリのコピー
    3. ファイルの移動
    4. ディレクトリの移動
  6. ファイルサイズの取得
    1. ファイルサイズの取得方法
    2. 詳細なエラーハンドリング
    3. ディレクトリのサイズ計算
  7. ディレクトリ内のファイル一覧の取得
    1. ディレクトリ内のファイル一覧の取得方法
    2. 再帰的なファイル一覧の取得
    3. 特定のファイルのみをリストアップ
  8. パス操作と正規化
    1. パスの結合
    2. パスの正規化
    3. 相対パスと絶対パスの変換
    4. パスの親ディレクトリの取得
  9. 応用例:再帰的なディレクトリ操作
    1. ディレクトリ内のすべてのファイルを削除
    2. ディレクトリサイズの再帰的な計算
    3. 特定のファイルを再帰的に検索
  10. エラーハンドリング
    1. 基本的なエラーハンドリング
    2. ファイルのコピー時のエラーハンドリング
    3. ディレクトリ操作時のエラーハンドリング
    4. エラーメッセージのカスタマイズ
  11. 演習問題と解答例
    1. 演習問題1: ファイルの存在確認とコピー
    2. 演習問題2: ディレクトリ内のファイル一覧を取得
    3. 演習問題3: ディレクトリ内のファイルサイズの合計を計算
    4. 演習問題4: ファイルの移動とリネーム
  12. まとめ