閉じる

C++でディレクトリを再帰的に削除する方法:中身ごと一括で削除する

C++でプログラムを開発している際、不要になった一時フォルダやログディレクトリを中身ごと削除したい場面は多々あります。

しかし、標準的なファイル削除関数では「空ではないディレクトリ」を一度に消すことができず、以前はOS固有のAPIを呼び出すか、複雑な再帰処理を自作する必要がありました。

C++17以降では、標準ライブラリのfilesystemが導入されたことで、この問題は非常にシンプルに解決できるようになっています。

本記事では、初心者から中級者までが安心して使える「ディレクトリの再帰的削除」の決定版を解説します。

C++におけるディレクトリ削除の基本

C++でディレクトリを操作する場合、かつてはプラットフォーム(Windows/Linux)ごとに異なるコードを書く必要がありました。

現代のC++開発においては、C++17で追加されたstd::filesystemライブラリを使用するのが標準的です。

std::filesystem::remove と remove_all の違い

ディレクトリを削除するための関数には、主にremoveremove_allの2種類が存在します。

これらは似て非なる動作をするため、正しく使い分けることが重要です。

remove関数:空のディレクトリのみ削除可能

std::filesystem::removeは、指定したファイル、あるいは「空のディレクトリ」を削除するための関数です。

もしディレクトリの中に一つでもファイルやサブフォルダが含まれている場合、この関数は失敗し、ディレクトリを削除することはできません。

remove_all関数:中身を含めて一括削除

今回のテーマである「再帰的な削除」を実現するのがstd::filesystem::remove_allです。

この関数は、指定されたパスがディレクトリであれば、その中のファイルや子ディレクトリをすべて走査し、中身をすべて削除してから自分自身を削除します。

関数名削除対象中身があるディレクトリ戻り値
removeファイル / 空ディレクトリエラー (削除不可)bool (削除成否)
remove_allファイル / ディレクトリ全般再帰的にすべて削除uintmax_t (削除した項目数)

C++17 標準ライブラリによる一括削除の実装

それでは、実際にstd::filesystem::remove_allを使用したコードを見ていきましょう。

この方法は現在最も推奨される、簡潔かつ安全な方法です。

標準関数を使ったサンプルコード

このプログラムでは、指定したディレクトリを再帰的に削除し、最終的に何個のファイルやディレクトリが削除されたかを表示します。

C++
#include <iostream>
#include <filesystem> // C++17から導入されたライブラリ
#include <system_error>

namespace fs = std::filesystem;

int main() {
    // 削除対象のパスを指定
    fs::path target_path = "temp_directory";

    // 事前にディレクトリが存在するか確認
    if (!fs::exists(target_path)) {
        std::cout << "指定されたパスが存在しません: " << target_path << std::endl;
        return 1;
    }

    try {
        // 再帰的にディレクトリとその中身を削除
        // 戻り値は削除されたファイルやディレクトリの合計数
        std::uintmax_t deleted_count = fs::remove_all(target_path);

        std::cout << "削除が完了しました。" << std::endl;
        std::cout << "削除された項目数: " << deleted_count << std::endl;
    }
    catch (const fs::filesystem_error& e) {
        // エラーが発生した場合の詳細表示
        std::cerr << "エラーが発生しました: " << e.what() << std::endl;
    }

    return 0;
}
実行結果
削除が完了しました。
削除された項目数: 15

エラーハンドリングの重要性

ディレクトリの削除は、常に成功するとは限りません。

例えば、他のプログラムがそのディレクトリ内のファイルを開いている(ロックしている)場合や、実行ユーザーに削除権限がない場合は例外がスローされます。

上記のコードではtry-catchブロックを使用していますが、例外を投げたくない場合は、第二引数にstd::error_codeを渡す方法もあります。

C++
std::error_code ec;
std::uintmax_t deleted_count = fs::remove_all(target_path, ec);

if (ec) {
    std::cerr << "エラーコード: " << ec.value() << " メッセージ: " << ec.message() << std::endl;
}

このように実装することで、プログラムの異常終了を防ぎ、エラー発生時の処理を柔軟に記述できます。

自作による再帰的削除アルゴリズムの理解

ライブラリ関数を使えば一行で済む処理ですが、その内部でどのような処理が行われているかを知ることは、プログラミングスキルの向上に繋がります。

ここでは、ディレクトリイテレータを使って、自力で再帰削除を実装するロジックを解説します。

再帰的トラバーサルの仕組み

ディレクトリを削除する場合、必ず「子から親へ」の順番で削除しなければなりません。

親を先に消してしまうと、その中にアクセスできなくなるためです。

これをデータ構造の用語では「後順走査(ポストオーダー)」と呼びます。

自作関数の実装例

directory_iteratorを使用して、ディレクトリを一つずつ走査するコード例です。

C++
#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

// 自作の再帰削除関数
void custom_recursive_remove(const fs::path& path) {
    if (!fs::exists(path)) return;

    if (fs::is_directory(path)) {
        // ディレクトリ内のアイテムを走査
        for (const auto& entry : fs::directory_iterator(path)) {
            // 再帰的に自分自身を呼び出す
            custom_recursive_remove(entry.path());
        }
    }

    // 中身が空になった、またはファイルであるため削除可能
    std::cout << "削除中: " << path << std::endl;
    fs::remove(path);
}

int main() {
    fs::path target = "test_dir";
    
    // 自作関数による実行
    custom_recursive_remove(target);
    
    return 0;
}

自作する場合の注意点

この自作コードは学習用としては有用ですが、実際の製品開発ではstd::filesystem::remove_allを使用してください。

標準関数は、シンボリックリンクの扱い(リンク先を消さずにリンクだけ消す等)や、OS固有の権限問題などが最適化されているためです。

OS固有の制限と注意点

C++の標準ライブラリはOS間の差異を吸収してくれますが、根本的なOSの仕様までを変えることはできません。

ディレクトリ削除時に遭遇しやすいトラブルを整理します。

Windowsにおけるファイルロック

Windows OSでは、「他のプロセスがファイルを開いている間は削除できない」という制約が強力です。

例えば、テキストエディタでログファイルを開いたままプログラムを実行すると、remove_allは途中で失敗します。

この場合、リトライ処理を入れるか、ユーザーにファイルを閉じるよう促す必要があります。

Linuxにおけるアクセス権限

LinuxやmacOSでは、ディレクトリ自体の書き込み権限(w)および実行権限(x)がないと、その中のファイルを削除することができません。

root権限が必要なディレクトリを削除しようとすると、Permission deniedエラーが発生します。

シンボリックリンクの挙動

remove_allは、シンボリックリンクそのものを削除しますが、リンク先のディレクトリの中身まで削除することはありません。

これはデータ破壊を防ぐための安全な仕様です。

もしリンク先のデータも消したい場合は、別途ロジックを組む必要があります。

安全な削除のためのベストプラクティス

ディレクトリの再帰的削除は非常に強力な操作であり、一歩間違えるとシステムにとって重要なデータを消失させるリスクがあります。

安全に運用するためのポイントをいくつか紹介します。

1. ルートディレクトリ等の保護

不注意な変数指定により、remove_all("/")remove_all("C:\")が実行されないよう、パスの検証を行うべきです。

C++
void safe_remove_all(const fs::path& path) {
    // 絶対パスを取得
    fs::path abs_path = fs::absolute(path);
    
    // 非常に浅い階層やルートディレクトリは拒否する例
    if (abs_path.relative_path().empty() || abs_path == "/") {
        throw std::runtime_error("危険なパスが指定されました");
    }
    
    fs::remove_all(abs_path);
}

2. 削除前の確認

重要なデータを扱うツールの場合、いきなり消すのではなく、削除対象のリストを一度表示し、ユーザーの確認(Y/N)を求めるステップを挟むのが一般的です。

3. ゴミ箱への移動 (OS依存)

std::filesystemには「ゴミ箱に移動する」という標準機能はありません。

完全に抹消したくない場合は、OS固有のAPI(WindowsならSHFileOperationなど)を利用することを検討してください。

まとめ

C++でディレクトリを再帰的に中身ごと削除するには、C++17以降のstd::filesystem::remove_allを使用するのが最も効率的で安全な方法です。

この関数は、サブディレクトリやファイルを自動的にトラバースして一括削除してくれるため、かつてのような複雑な再帰関数を自作する必要はありません。

実装の際は、ファイルロックやアクセス権限によるエラーに備えて、適切な例外処理(try-catch)やエラーコードのチェックを行うことが実務上の重要なポイントとなります。

また、強力な関数であるからこそ、削除対象のパスが意図しない場所を指していないか、事前にバリデーションを行う習慣をつけましょう。

今回紹介した手法をマスターすることで、C++におけるファイル操作の自由度が大きく向上するはずです。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!