C++ File Operations | '파일 연산' 가이드
이 글의 핵심
C++ File Operations: "파일 연산" 가이드. 파일 연산 기본·디렉토리 연산.
들어가며
C++17 <filesystem> 라이브러리는 파일과 디렉토리 조작을 위한 표준 API를 제공합니다. 이전의 플랫폼별 API(POSIX, Windows API)를 대체하여 이식성 높은 코드를 작성할 수 있습니다.
1. 파일 연산 기본
C++17 이전에는 파일 존재 여부 확인이나 디렉토리 생성 같은 기본적인 작업조차 플랫폼마다 다른 API(POSIX의 stat, Windows의 CreateDirectory 등)를 직접 호출해야 했습니다. <filesystem> 헤더는 이런 플랫폼 종속성을 표준 인터페이스 뒤로 감춰, 동일한 코드를 리눅스·macOS·윈도우에서 그대로 컴파일하고 실행할 수 있게 해줍니다.
기본 설정
std::filesystem은 관례적으로 fs라는 짧은 네임스페이스 별칭으로 사용됩니다. fs::exists()와 fs::file_size()처럼 이름 그대로의 함수들이 제공되므로, 별도의 학습 곡선 없이도 파일 시스템을 다루는 코드를 직관적으로 작성할 수 있습니다.
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
// 파일 존재 확인
if (fs::exists("test.txt")) {
std::cout << "파일 존재" << std::endl;
}
// 파일 크기
auto size = fs::file_size("test.txt");
std::cout << "크기: " << size << " bytes" << std::endl;
return 0;
}
파일 복사
fs::copy_file()은 기본적으로 대상 파일이 이미 존재하면 예외를 던지도록 설계되어 있는데, 이는 실수로 기존 파일을 덮어쓰는 사고를 방지하기 위한 안전장치입니다. 의도적으로 덮어써야 한다면 copy_options::overwrite_existing을 명시적으로 지정해야 하며, 디렉토리 전체를 복사할 때는 fs::copy_file() 대신 fs::copy()에 recursive 옵션을 함께 넘겨야 하위 파일까지 모두 복사됩니다.
// 기본 복사
fs::copy_file("src.txt", "dst.txt");
// 덮어쓰기
fs::copy_file("src.txt", "dst.txt",
fs::copy_options::overwrite_existing);
// 디렉토리 복사 (재귀)
fs::copy("src_dir", "dst_dir",
fs::copy_options::recursive);
파일 이동 및 삭제
fs::rename()은 이름 그대로 파일 이름을 바꾸는 함수이지만, 같은 파일시스템 안에서는 실제 데이터를 옮기지 않고 디렉토리 항목의 메타데이터만 갱신하기 때문에 사실상 “이동”으로도 쓸 수 있고 속도도 매우 빠릅니다. fs::remove()는 파일 하나 또는 빈 디렉토리 하나만 삭제할 수 있으므로, 내용물이 있는 디렉토리를 통째로 지우려면 반드시 fs::remove_all()을 사용해야 합니다.
// 이름 변경 (이동)
fs::rename("old.txt", "new.txt");
// 파일 삭제
fs::remove("file.txt");
// 디렉토리 삭제 (재귀)
fs::remove_all("dir");
2. 디렉토리 연산
파일 하나를 다루는 것과 디렉토리 구조 전체를 다루는 것은 요구되는 API가 다릅니다. 디렉토리를 생성하고, 그 안의 내용을 순회하고, 재귀적으로 하위 디렉토리까지 탐색하는 작업은 <filesystem>이 제공하는 별도의 함수와 반복자를 통해 처리합니다.
디렉토리 생성
fs::create_directory()는 단일 계층의 디렉토리만 만들 수 있어서, 중간 경로가 존재하지 않으면 실패합니다. 예를 들어 a/b/c처럼 여러 단계의 경로를 한 번에 만들어야 한다면 fs::create_directories()(복수형)를 사용해야 하며, 이 함수는 이미 존재하는 중간 디렉토리는 건너뛰고 없는 것만 생성합니다.
// 단일 디렉토리
fs::create_directory("mydir");
// 중첩 디렉토리 (재귀)
fs::create_directories("path/to/dir");
// 이미 존재하면 false 반환
bool created = fs::create_directory("existing_dir");
if (!created) {
std::cout << "이미 존재하거나 생성 실패" << std::endl;
}
디렉토리 순회
fs::directory_iterator는 지정한 디렉토리의 바로 아래 항목만 순회하는 반면, fs::recursive_directory_iterator는 하위 디렉토리 안으로 계속 내려가면서 트리 전체를 순회합니다. 각 항목이 파일인지 디렉토리인지는 entry.is_regular_file(), entry.is_directory()로 구분할 수 있으며, 이 두 반복자는 범위 기반 for 문과 자연스럽게 결합되어 파일 검색이나 일괄 처리 스크립트를 작성할 때 핵심적인 도구로 쓰입니다.
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
// 현재 디렉토리 순회
for (const auto& entry : fs::directory_iterator(".")) {
std::cout << entry.path() << std::endl;
}
// 재귀 순회
for (const auto& entry : fs::recursive_directory_iterator(".")) {
if (entry.is_regular_file()) {
std::cout << "파일: " << entry.path() << std::endl;
} else if (entry.is_directory()) {
std::cout << "디렉토리: " << entry.path() << std::endl;
}
}
return 0;
}
3. 복사 옵션
fs::copy_file()과 fs::copy()의 기본 동작은 대상이 이미 존재하면 실패하는 보수적인 방식이지만, 실무에서는 백업·동기화·배포처럼 상황에 따라 덮어쓰기, 건너뛰기, 최신 파일만 갱신하기 등 서로 다른 정책이 필요합니다. copy_options 열거형은 이런 다양한 정책을 플래그 조합으로 표현할 수 있게 해줍니다.
copy_options 플래그
각 옵션은 서로 다른 시나리오에 대응합니다. overwrite_existing은 항상 덮어써야 하는 배포 스크립트에, skip_existing은 이미 처리된 파일을 다시 건드리지 않아야 하는 증분 작업에, update_existing은 원본이 더 최신일 때만 복사하는 동기화 도구에 적합합니다. 여러 옵션은 비트 OR 연산자(|)로 조합해서 함께 지정할 수도 있습니다.
#include <filesystem>
namespace fs = std::filesystem;
// 기본: 이미 존재하면 에러
fs::copy_file("src.txt", "dst.txt");
// 덮어쓰기
fs::copy_file("src.txt", "dst.txt",
fs::copy_options::overwrite_existing);
// 건너뛰기 (이미 존재하면 무시)
fs::copy_file("src.txt", "dst.txt",
fs::copy_options::skip_existing);
// 업데이트 (더 최신이면 복사)
fs::copy_file("src.txt", "dst.txt",
fs::copy_options::update_existing);
// 재귀 복사 (디렉토리)
fs::copy("src_dir", "dst_dir",
fs::copy_options::recursive);
// 심볼릭 링크 복사 (링크 자체)
fs::copy("link", "new_link",
fs::copy_options::copy_symlinks);
디렉토리를 백업하거나 배포할 때는 아래처럼 recursive와 overwrite_existing을 함께 지정하는 조합이 특히 자주 쓰입니다. 하위 디렉토리까지 전부 복사하면서도 이미 존재하는 대상 파일을 최신 내용으로 갱신할 수 있기 때문입니다.
copy_options 조합:
// 재귀 + 덮어쓰기
fs::copy("src_dir", "dst_dir",
fs::copy_options::recursive |
fs::copy_options::overwrite_existing);
4. 실전 예제
지금까지 배운 개별 함수들을 실제 업무에서 마주치는 네 가지 시나리오 — 안전한 복사, 타임스탬프 백업, 오래된 파일 정리, 디렉토리 동기화 — 로 조합해 보겠습니다. 각 예제는 실무에서 그대로 가져다 쓸 수 있을 정도로 예외 처리까지 포함한 완성된 형태입니다.
예제 1: 안전한 파일 복사
파일 복사 코드를 프로덕션에 배포하려면 최소 세 가지를 확인해야 합니다. 원본이 실제로 존재하는지, 대상 디렉토리가 미리 만들어져 있는지, 그리고 복사 도중 발생할 수 있는 예외를 어떻게 처리할지입니다. 아래 copyFileSafe()는 이 세 가지를 모두 다루면서, 성공과 실패를 bool 반환값으로 명확하게 알려주는 형태로 설계되었습니다.
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
bool copyFileSafe(const fs::path& src, const fs::path& dst) {
try {
// 원본 파일 존재 확인
if (!fs::exists(src)) {
std::cerr << "원본 파일이 존재하지 않습니다: " << src << std::endl;
return false;
}
// 대상 디렉토리 생성
fs::create_directories(dst.parent_path());
// 파일 복사
fs::copy_file(src, dst,
fs::copy_options::overwrite_existing);
std::cout << "복사 완료: " << src << " -> " << dst << std::endl;
return true;
} catch (const fs::filesystem_error& e) {
std::cerr << "에러: " << e.what() << std::endl;
return false;
}
}
int main() {
copyFileSafe("data/input.txt", "backup/input.txt");
return 0;
}
예제 2: 디렉토리 백업
동일한 이름으로 백업을 반복 실행하면 이전 백업을 덮어써 버리는 문제가 생기므로, 백업 디렉토리 이름에 타임스탬프를 붙여 매번 고유한 경로를 만드는 것이 일반적인 관례입니다. 아래 backupDirectory()는 std::chrono로 현재 시각을 문자열로 포맷한 뒤 원본 디렉토리 이름 뒤에 덧붙여, 언제 백업했는지가 경로 이름만으로 드러나도록 만듭니다.
#include <filesystem>
#include <iostream>
#include <chrono>
#include <iomanip>
#include <sstream>
namespace fs = std::filesystem;
std::string getCurrentTimestamp() {
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&time), "%Y%m%d_%H%M%S");
return ss.str();
}
void backupDirectory(const fs::path& src, const fs::path& backupRoot) {
try {
// 타임스탬프로 백업 디렉토리 생성
fs::path backupPath = backupRoot / (src.filename().string() + "_" + getCurrentTimestamp());
// 재귀 복사
fs::copy(src, backupPath,
fs::copy_options::recursive |
fs::copy_options::overwrite_existing);
std::cout << "백업 완료: " << backupPath << std::endl;
} catch (const fs::filesystem_error& e) {
std::cerr << "백업 실패: " << e.what() << std::endl;
}
}
int main() {
backupDirectory("project", "backups");
// 결과: backups/project_20260329_143025/
return 0;
}
예제 3: 오래된 파일 정리
로그 파일이나 임시 파일이 계속 쌓이는 디렉토리는 주기적으로 오래된 항목을 정리해주지 않으면 디스크 공간을 서서히 잠식합니다. 아래 cleanupOldFiles()는 fs::last_write_time()으로 각 파일의 마지막 수정 시각을 확인하고, 지정한 보관 기간(days)보다 오래된 파일만 골라 삭제하면서 삭제 전 크기와 개수를 로그로 남겨 나중에 얼마나 정리되었는지 추적할 수 있게 합니다.
#include <filesystem>
#include <iostream>
#include <chrono>
namespace fs = std::filesystem;
void cleanupOldFiles(const fs::path& dir, int days) {
try {
auto now = fs::file_time_type::clock::now();
auto threshold = now - std::chrono::hours(24 * days);
int deletedCount = 0;
for (const auto& entry : fs::directory_iterator(dir)) {
if (entry.is_regular_file()) {
auto mtime = fs::last_write_time(entry);
if (mtime < threshold) {
auto size = fs::file_size(entry);
fs::remove(entry.path());
std::cout << "삭제: " << entry.path()
<< " (" << size << " bytes)" << std::endl;
deletedCount++;
}
}
}
std::cout << "총 " << deletedCount << "개 파일 삭제" << std::endl;
} catch (const fs::filesystem_error& e) {
std::cerr << "에러: " << e.what() << std::endl;
}
}
int main() {
cleanupOldFiles("temp", 7); // 7일 이상 된 파일 삭제
return 0;
}
예제 4: 파일 동기화
두 디렉토리를 동기화하는 로직은 단순히 전체를 복사하는 것보다 훨씬 효율적으로 설계할 수 있습니다. 아래 syncDirectories()는 원본을 재귀적으로 순회하면서, 대상에 해당 파일이 아직 없거나 원본이 더 최신인 경우에만 복사를 수행합니다. fs::relative()로 원본 기준 상대 경로를 구한 뒤 대상 루트에 이어붙이는 방식은, 디렉토리 구조 자체를 그대로 보존하면서 필요한 파일만 골라 옮기는 전형적인 동기화 도구의 뼈대입니다.
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
void syncDirectories(const fs::path& src, const fs::path& dst) {
try {
for (const auto& entry : fs::recursive_directory_iterator(src)) {
auto relativePath = fs::relative(entry.path(), src);
auto dstPath = dst / relativePath;
if (entry.is_directory()) {
// 디렉토리 생성
fs::create_directories(dstPath);
} else if (entry.is_regular_file()) {
// 파일이 없거나 더 최신이면 복사
if (!fs::exists(dstPath) ||
fs::last_write_time(entry) > fs::last_write_time(dstPath)) {
fs::copy_file(entry.path(), dstPath,
fs::copy_options::overwrite_existing);
std::cout << "동기화: " << relativePath << std::endl;
}
}
}
} catch (const fs::filesystem_error& e) {
std::cerr << "에러: " << e.what() << std::endl;
}
}
int main() {
syncDirectories("source", "destination");
return 0;
}
5. 자주 발생하는 문제
파일 시스템 API는 네트워크나 권한 문제처럼 프로그램이 통제할 수 없는 외부 요인에 크게 좌우되기 때문에, 실무에서는 아래 네 가지 유형의 문제를 특히 자주 마주치게 됩니다. 각각의 원인을 이해하면 예외 메시지만 보고도 어디를 고쳐야 할지 빠르게 짐작할 수 있습니다.
문제 1: 덮어쓰기 에러
fs::copy_file()을 옵션 없이 호출했는데 대상 파일이 이미 있다면, 표준은 이를 실수로 간주하고 명시적으로 filesystem_error 예외를 던지도록 정의하고 있습니다. 이 동작 자체가 버그가 아니라 의도된 안전장치이므로, “왜 복사가 실패하지?”라는 의문이 든다면 가장 먼저 대상 파일의 존재 여부와 copy_options 지정 여부를 확인해야 합니다.
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
// ❌ 이미 존재하면 에러
try {
fs::copy_file("src.txt", "existing.txt");
} catch (const fs::filesystem_error& e) {
std::cerr << "에러: " << e.what() << std::endl;
// filesystem error: cannot copy file: File exists
}
// ✅ 옵션 지정
fs::copy_file("src.txt", "existing.txt",
fs::copy_options::overwrite_existing);
return 0;
}
해결책: copy_options를 명시적으로 지정하세요.
문제 2: 디렉토리 삭제 실패
fs::remove()는 디렉토리 안에 파일이 하나라도 남아 있으면 안전을 위해 삭제를 거부하도록 설계되어 있습니다. 이는 실수로 중요한 데이터가 담긴 디렉토리 전체가 삭제되는 것을 막기 위한 설계이며, 내용물까지 포함해 전체를 지우고 싶다는 의도를 명확히 표현하려면 이름부터 다른 fs::remove_all()을 명시적으로 호출해야 합니다.
// ❌ 비어있지 않은 디렉토리
try {
fs::remove("non_empty_dir"); // 실패!
} catch (const fs::filesystem_error& e) {
std::cerr << e.what() << std::endl;
// Directory not empty
}
// ✅ 재귀 삭제
int removed = fs::remove_all("non_empty_dir");
std::cout << removed << "개 항목 삭제" << std::endl;
해결책: 비어있지 않은 디렉토리는 remove_all()을 사용하세요.
문제 3: 이동 vs 복사
fs::rename()이 항상 성공하는 것은 아닙니다. 같은 파티션이나 볼륨 안에서는 디렉토리 항목만 바꾸면 되므로 즉시 성공하지만, 서로 다른 파일시스템(예: 다른 디스크나 다른 마운트 지점) 사이에서는 운영체제가 이 연산을 지원하지 않아 예외가 발생합니다. 이런 경우를 대비해 rename()을 먼저 시도하고 실패하면 copy_file() + remove()로 대체하는 폴백 전략을 짜두는 것이 견고한 파일 이동 함수를 만드는 핵심입니다.
// 이동 (빠름, 원자적)
try {
fs::rename("old.txt", "new.txt");
// 같은 파일시스템 내에서만 가능
} catch (const fs::filesystem_error& e) {
// 다른 파일시스템이면 실패
std::cerr << "이동 실패: " << e.what() << std::endl;
}
// 복사 + 삭제 (느림, 비원자적)
fs::copy_file("src.txt", "dst.txt");
fs::remove("src.txt");
// 중간에 실패하면 두 파일이 모두 존재할 수 있음
해결책: 같은 파일시스템 내에서는 rename(), 다른 파일시스템으로는 copy() + remove()를 사용하세요.
문제 4: 예외 처리
<filesystem>의 대부분 함수는 두 가지 오버로드를 제공합니다. 기본 오버로드는 실패 시 fs::filesystem_error 예외를 던지고, 마지막 인자로 std::error_code&를 넘기는 오버로드는 예외 대신 그 인자에 오류 정보를 채워 넣습니다. 예외 처리 오버헤드를 피하고 싶은 성능 민감 코드나, 예외를 아예 쓰지 않는 코드베이스(임베디드, 게임 엔진 등)에서는 error_code 버전을 사용하는 것이 관례입니다.
#include <filesystem>
#include <iostream>
#include <system_error>
namespace fs = std::filesystem;
// ❌ 예외 무시 (위험)
void deleteFileUnsafe(const fs::path& path) {
fs::remove(path); // 실패 시 예외 발생
}
// ✅ try-catch
void deleteFileSafe1(const fs::path& path) {
try {
fs::remove(path);
} catch (const fs::filesystem_error& e) {
std::cerr << "삭제 실패: " << e.what() << std::endl;
}
}
// ✅ error_code (예외 없음)
void deleteFileSafe2(const fs::path& path) {
std::error_code ec;
bool removed = fs::remove(path, ec);
if (ec) {
std::cerr << "삭제 실패: " << ec.message() << std::endl;
} else if (removed) {
std::cout << "삭제 완료" << std::endl;
} else {
std::cout << "파일이 존재하지 않음" << std::endl;
}
}
해결책: 예외 처리를 항상 고려하거나 error_code 버전을 사용하세요.
6. 원자적 연산
파일 시스템 연산에서 “원자적(atomic)“이라는 말은, 그 연산이 중간에 실패하더라도 절반만 적용된 상태가 절대 남지 않는다는 것을 의미합니다. 이 성질은 데이터 무결성이 중요한 상황(설정 파일 교체, 로그 로테이션 등)에서 매우 중요하며, <filesystem>의 연산 중 어떤 것이 원자적이고 어떤 것이 아닌지 구분해서 사용하는 것이 안전한 코드를 만드는 핵심입니다.
rename의 원자성
같은 파일시스템 안에서의 fs::rename()은 운영체제 커널이 디렉토리 항목을 교체하는 단일 연산으로 처리하므로, 도중에 프로세스가 죽거나 전원이 꺼져도 이름 변경 전이나 후, 둘 중 하나의 상태만 존재하게 됩니다. 이 성질 덕분에 설정 파일을 임시 파일에 먼저 다 쓴 다음 rename()으로 교체하는 “쓰기 후 교체(write-then-rename)” 패턴이 안전한 파일 갱신 기법으로 널리 쓰입니다.
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
// rename은 원자적 (같은 파일시스템)
// 성공하거나 실패하거나, 중간 상태 없음
try {
fs::rename("old.txt", "new.txt");
std::cout << "이동 완료 (원자적)" << std::endl;
} catch (const fs::filesystem_error& e) {
std::cerr << "이동 실패: " << e.what() << std::endl;
}
return 0;
}
비원자적 복사
반면 copy_file() + remove()로 파일을 “이동”하는 방식은 두 개의 독립적인 연산을 순서대로 실행하는 것이므로, 복사는 성공했지만 삭제 단계에서 예외나 프로세스 종료가 발생하면 원본과 사본이 모두 남아있는 어중간한 상태가 될 수 있습니다. 이런 특성 때문에 서로 다른 파일시스템 간 이동이 꼭 필요한 경우가 아니라면, 가능한 한 원자적인 rename()을 우선적으로 시도하는 것이 안전합니다.
// copy + remove는 비원자적
// 중간에 실패하면 불일치 상태 발생 가능
bool moveFile(const fs::path& src, const fs::path& dst) {
try {
// 1. 복사
fs::copy_file(src, dst);
// 2. 삭제 (여기서 실패하면 두 파일 모두 존재)
fs::remove(src);
return true;
} catch (const fs::filesystem_error& e) {
std::cerr << "이동 실패: " << e.what() << std::endl;
return false;
}
}
앞의 두 특성을 조합하면, “가능하면 빠르고 안전하게, 안 되면 대체 수단으로”라는 실용적인 이동 함수를 만들 수 있습니다. 아래 atomicMove()는 먼저 rename()을 시도하고, 서로 다른 파일시스템이라 실패하면 그때만 copy_file() + remove()로 대체하는 방어적인 구조를 취하고 있습니다.
원자적 이동 패턴:
bool atomicMove(const fs::path& src, const fs::path& dst) {
try {
// 같은 파일시스템이면 rename (원자적)
fs::rename(src, dst);
return true;
} catch (const fs::filesystem_error&) {
// 다른 파일시스템이면 copy + remove
try {
fs::copy_file(src, dst);
fs::remove(src);
return true;
} catch (const fs::filesystem_error& e) {
std::cerr << "이동 실패: " << e.what() << std::endl;
return false;
}
}
}
7. 파일 연산 비교
지금까지 다룬 네 가지 핵심 연산을 원자성, 파일시스템 간 이동 가능 여부, 상대적 속도라는 세 기준으로 정리하면 아래와 같습니다. 상황에 맞는 연산을 고를 때 이 표를 기준으로 판단하면 됩니다.
| 연산 | 함수 | 원자성 | 파일시스템 간 | 속도 |
|---|---|---|---|---|
| 복사 | copy_file() | ✗ | ✓ | 느림 |
| 이동 | rename() | ✓ | ✗ | 빠름 |
| 삭제 | remove() | ✓ | - | 빠름 |
| 재귀 삭제 | remove_all() | ✗ | - | 느림 |
8. 실전 예제: 파일 관리자
지금까지 배운 예외 처리, 원자적 이동, 정리, 크기 계산을 하나의 클래스로 묶으면 실무에서 재사용 가능한 작은 파일 관리 유틸리티를 만들 수 있습니다. 아래 FileManager는 모든 연산에서 예외 대신 std::error_code를 사용하도록 통일해, 예외 처리 오버헤드 없이도 각 연산의 성공 여부를 세밀하게 제어할 수 있는 구조로 설계되었습니다.
#include <filesystem>
#include <iostream>
#include <vector>
namespace fs = std::filesystem;
class FileManager {
public:
// 파일 복사 (안전)
bool copy(const fs::path& src, const fs::path& dst) {
std::error_code ec;
if (!fs::exists(src, ec)) {
std::cerr << "원본 파일 없음: " << src << std::endl;
return false;
}
fs::create_directories(dst.parent_path(), ec);
fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec);
if (ec) {
std::cerr << "복사 실패: " << ec.message() << std::endl;
return false;
}
return true;
}
// 파일 이동 (원자적)
bool move(const fs::path& src, const fs::path& dst) {
std::error_code ec;
// 먼저 rename 시도 (빠름)
fs::rename(src, dst, ec);
if (!ec) {
return true;
}
// 실패하면 copy + remove
if (copy(src, dst)) {
fs::remove(src, ec);
return !ec;
}
return false;
}
// 오래된 파일 정리
int cleanup(const fs::path& dir, int days) {
std::error_code ec;
auto now = fs::file_time_type::clock::now();
auto threshold = now - std::chrono::hours(24 * days);
int count = 0;
for (const auto& entry : fs::directory_iterator(dir, ec)) {
if (entry.is_regular_file(ec)) {
auto mtime = fs::last_write_time(entry, ec);
if (!ec && mtime < threshold) {
fs::remove(entry.path(), ec);
if (!ec) {
count++;
}
}
}
}
return count;
}
// 디렉토리 크기 계산
uintmax_t directorySize(const fs::path& dir) {
std::error_code ec;
uintmax_t size = 0;
for (const auto& entry : fs::recursive_directory_iterator(dir, ec)) {
if (entry.is_regular_file(ec)) {
size += fs::file_size(entry, ec);
}
}
return size;
}
};
int main() {
FileManager fm;
// 파일 복사
fm.copy("data.txt", "backup/data.txt");
// 파일 이동
fm.move("temp.txt", "archive/temp.txt");
// 7일 이상 된 파일 정리
int deleted = fm.cleanup("temp", 7);
std::cout << deleted << "개 파일 삭제" << std::endl;
// 디렉토리 크기
auto size = fm.directorySize("project");
std::cout << "프로젝트 크기: " << size << " bytes" << std::endl;
return 0;
}
정리
C++17의 <filesystem> 라이브러리는 플랫폼 종속적이던 파일 시스템 API를 표준화하면서, 동시에 예외와 error_code라는 두 가지 오류 처리 방식을 함께 제공해 다양한 코드 스타일에 유연하게 대응할 수 있게 해줍니다. 아래 요약과 선택 가이드로 지금까지의 핵심 내용을 정리합니다.
핵심 요약
- 복사:
copy_file(),copy()(재귀) - 이동:
rename()(원자적, 같은 파일시스템) - 삭제:
remove(),remove_all()(재귀) - 디렉토리:
create_directories()(재귀 생성) - 옵션:
copy_options(덮어쓰기, 건너뛰기, 업데이트) - 예외:
try-catch또는error_code사용
파일 연산 선택 가이드
| 상황 | 권장 방법 | 이유 |
|---|---|---|
| 같은 파일시스템 이동 | rename() | 빠르고 원자적 |
| 다른 파일시스템 이동 | copy() + remove() | rename() 불가 |
| 백업 | copy() | 원본 유지 |
| 동기화 | last_write_time() 비교 | 변경된 파일만 |
| 대용량 파일 | rename() 우선 | 복사 비용 절감 |
실전 팁
안전성:
- 항상 예외 처리 (
try-catch또는error_code) - 원본 파일 존재 확인 후 작업
- 대상 디렉토리 미리 생성 (
create_directories)
성능:
- 이동은
rename()우선 시도 (빠름) - 대용량 파일은 복사 대신 이동
- 재귀 작업은
recursive_directory_iterator사용
유지보수:
error_code버전으로 예외 없는 코드 작성- 로깅으로 작업 내역 기록
- 트랜잭션 패턴으로 롤백 가능하게 설계
다음 단계
관련 글
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Everything about C++ File Operations : from basic concepts to practical applications.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Filesystem 빠른 참조 | ‘파일시스템’ C++17 라이브러리 가이드
- C++ path | ‘경로 처리’ 가이드
- C++ Chrono Literals | ‘시간 리터럴’ 가이드
이 글에서 다루는 키워드 (관련 검색어)
C++, file-operations, filesystem, C++17, copy 등으로 검색하시면 이 글이 도움이 됩니다.