首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 遞歸刪除目錄樹完整實現:SHFileOperation 替代方案與最佳實踐

     編輯:關於C++

Windows API 僅提供 RemoveDirectory 刪除**空**目錄。實際開發中常需刪除包含子目錄、檔案的整個目錄樹。本文提供基於 FindFirstFile/FindNextFile 遞歸枚舉的完整 C++ 實現,支援長路徑、唯讀/隱藏/系統屬性檔案、符號連結跳過、錯誤處理與進度回呼,並對比 SHFileOperation(FO_DELETE) 與現代 IFileOperation 的優劣。

核心遞歸刪除演算法

  1. 打開目錄枚舉 (FindFirstFile(path\\*.*))。
  2. 遍歷每一項 (跳過 .、..):
  3. 若為目錄 (FILE_ATTRIBUTE_DIRECTORY):遞歸呼叫刪除子目錄。
  4. 若為檔案:移除屬性 (SetFileAttributes(NORMAL)) -> DeleteFile。
  5. 枚舉結束 -> FindClose -> RemoveDirectory 刪除當前空目錄。

健壯版完整代碼 (C++17 / Windows API)


#include 
#include 
#include  // C++17 std::filesystem (現代首選)
#include 

namespace fs = std::filesystem;

// ===== 現代 C++17 一行解決 (生產環境首選) =====
bool DeleteDirectoryModern(const std::wstring& path) {
    try {
        // remove_all 遞歸刪除目錄樹,返回刪除的檔案/目錄總數
        uintmax_t count = fs::remove_all(path);
        std::wcout << L"Deleted " << count << L" items from " << path << std::endl;
        return true;
    } catch (const fs::filesystem_error& e) {
        std::wcerr << L"Error: " << e.what() << std::endl;
        return false;
    }
}

// ===== 經典 Windows API 實現 (無 C++17/跨平台需求/精細控制時使用) =====
bool DeleteDirectoryClassic(const std::wstring& path) {
    // 1. 規範化路徑,確保以 \ 結尾
    std::wstring searchPath = path;
    if (!searchPath.empty() && searchPath.back() != L'\\') searchPath += L'\\';
    searchPath += L"*.*";

    WIN32_FIND_DATAW findData;
    HANDLE hFind = FindFirstFileW(searchPath.c_str(), &findData);
    if (hFind == INVALID_HANDLE_VALUE) {
        DWORD err = GetLastError();
        if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) return true; // 不存在視為成功
        std::wcerr << L"FindFirstFile failed: " << err << L" for " << path << std::endl;
        return false;
    }

    bool success = true;
    do {
        // 跳過 . 和 ..
        if (wcscmp(findData.cFileName, L".") == 0 || wcscmp(findData.cFileName, L"..") == 0) continue;

        std::wstring fullPath = path + L'\\' + findData.cFileName;

        // 處理重解析點 (符號連結/接合點) - 只刪除連結本身,不遞歸進入目標
        if (findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
            if (!DeleteFileW(fullPath.c_str())) {
                std::wcerr << L"Delete reparse point failed: " << GetLastError() << L" " << fullPath << std::endl;
                success = false;
                break;
            }
            continue;
        }

        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            // 遞歸刪除子目錄
            if (!DeleteDirectoryClassic(fullPath)) {
                success = false;
                break;
            }
        } else {
            // 刪除檔案:先移除唯讀/隱藏/系統屬性
            DWORD attrs = GetFileAttributesW(fullPath.c_str());
            if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM))) {
                SetFileAttributesW(fullPath.c_str(), FILE_ATTRIBUTE_NORMAL);
            }
            if (!DeleteFileW(fullPath.c_str())) {
                DWORD err = GetLastError();
                // 可能檔案被佔用,嘗試 MoveFileEx 延遲刪除
                if (err == ERROR_SHARING_VIOLATION || err == ERROR_ACCESS_DENIED) {
                    if (!MoveFileExW(fullPath.c_str(), NULL, MOVEFILE_DELAY_UNTIL_REBOOT)) {
                        std::wcerr << L"Schedule delete on reboot failed: " << GetLastError() << L" " << fullPath << std::endl;
                        success = false;
                        break;
                    }
                } else {
                    std::wcerr << L"DeleteFile failed: " << err << L" " << fullPath << std::endl;
                    success = false;
                    break;
                }
            }
        }
    } while (FindNextFileW(hFind, &findData) && success);

    FindClose(hFind);

    if (success) {
        // 最後刪除空目錄
        if (!RemoveDirectoryW(path.c_str())) {
            DWORD err = GetLastError();
            if (err != ERROR_DIR_NOT_EMPTY && err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) {
                std::wcerr << L"RemoveDirectory failed: " << err << L" " << path << std::endl;
                success = false;
            }
        }
    }
    return success;
}

// ===== 使用範例 =====
int wmain(int argc, wchar_t* argv[]) {
    if (argc < 2) {
        std::wcout << L"Usage: " << argv[0] << L" " << std::endl;
        return 1;
    }
    // 推薦現代版
    DeleteDirectoryModern(argv[1]);
    // 或經典版
    // DeleteDirectoryClassic(argv[1]);
    return 0;
}

關鍵技術細節對比

特性std::filesystem::remove_all (C++17)手寫 WinAPI 遞歸SHFileOperation (FO_DELETE)IFileOperation (Vista+)
代碼量1 行~50 行~15 行~30 行 (COM)
長路徑 (>260)自動支援 (\\?\)需手動加前綴**不支援**支援
符號連結處理僅刪除連結可控 (代碼中判斷)可能遞歸進入目標 (危險)可控 (IOperationProgressSink)
進度/取消回呼無可自行添加有 (簡單)豐富 (IOperationProgressSink)
撤銷/資源回收筒無 (永久刪除)無支援 FOF_ALLOWUNDO支援
UAC/權限提升無自動提升無可觸發提升可整合 UAC
依賴C++17 STL僅 Windows SDKShell32.dllWindows 7+ / Shell32

最佳實踐建議

  1. 首選 std::filesystem::remove_all:現代 C++ 專案標配,簡潔、跨平台 (邏輯)、自動處理長路徑、異常安全。
  2. 需「刪除到資源回收筒」:使用 SHFileOperation (FOF_ALLOWUNDO) 或 IFileOperation (SetOperationFlags(FOF_ALLOWUNDO))。
  3. 需精細控制/進度 UI/取消/跳過特定檔案:手寫 WinAPI 遞歸或 IFileOperation + IOperationProgressSink。
  4. 處理檔案被佔用:MoveFileEx(..., MOVEFILE_DELAY_UNTIL_REBOOT) 排程重啟刪除 (需管理員權限)。
  5. 符號連結/接合點:務必檢查 FILE_ATTRIBUTE_REPARSE_POINT,**只刪除連結檔案本身**,切勿遞歸進入目標目錄 (可能導致刪除系統關鍵目錄或陷入循環)。
  6. 權限:刪除系統目錄、Program Files 需管理員權限 (Manifest `requireAdministrator` 或 UAC 提升)。
  7. Unicode:統一使用 Wide API (W 後綴) + std::wstring,避免中文路徑亂碼。

總結

遞歸刪除目錄樹看似簡單,實則涉及屬性處理、符號連結安全、長路徑、佔用檔案、權限提升、進度反饋等工程細節。**現代 C++ 專案請直接使用 std::filesystem::remove_all**;需資源回收筒支援用 IFileOperation;需極致控制或無 C++17 環境時,參考本文健壯版 WinAPI 實現並加入業務層邏輯 (進度回呼、錯誤重試、日誌記錄)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved