Windows API 僅提供 RemoveDirectory 刪除**空**目錄。實際開發中常需刪除包含子目錄、檔案的整個目錄樹。本文提供基於 FindFirstFile/FindNextFile 遞歸枚舉的完整 C++ 實現,支援長路徑、唯讀/隱藏/系統屬性檔案、符號連結跳過、錯誤處理與進度回呼,並對比 SHFileOperation(FO_DELETE) 與現代 IFileOperation 的優劣。
FindFirstFile(path\\*.*))。.、..):FILE_ATTRIBUTE_DIRECTORY):遞歸呼叫刪除子目錄。SetFileAttributes(NORMAL)) -> DeleteFile。FindClose -> RemoveDirectory 刪除當前空目錄。
#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 SDK | Shell32.dll | Windows 7+ / Shell32 |
std::filesystem::remove_all:現代 C++ 專案標配,簡潔、跨平台 (邏輯)、自動處理長路徑、異常安全。SHFileOperation (FOF_ALLOWUNDO) 或 IFileOperation (SetOperationFlags(FOF_ALLOWUNDO))。IFileOperation + IOperationProgressSink。MoveFileEx(..., MOVEFILE_DELAY_UNTIL_REBOOT) 排程重啟刪除 (需管理員權限)。FILE_ATTRIBUTE_REPARSE_POINT,**只刪除連結檔案本身**,切勿遞歸進入目標目錄 (可能導致刪除系統關鍵目錄或陷入循環)。W 後綴) + std::wstring,避免中文路徑亂碼。遞歸刪除目錄樹看似簡單,實則涉及屬性處理、符號連結安全、長路徑、佔用檔案、權限提升、進度反饋等工程細節。**現代 C++ 專案請直接使用 std::filesystem::remove_all**;需資源回收筒支援用 IFileOperation;需極致控制或無 C++17 環境時,參考本文健壯版 WinAPI 實現並加入業務層邏輯 (進度回呼、錯誤重試、日誌記錄)。