[2026] C++ File Operations | 파일 연산 가이드
이 글의 핵심
C++ File Operations: 파일 연산 가이드. 파일 연산 기본·디렉토리 연산.
들어가며
C++17 <filesystem> 라이브러리는 파일과 디렉토리 조작을 위한 표준 API를 제공합니다. 이전의 플랫폼별 API(POSIX, Windows API)를 대체하여 이식성 높은 코드를 작성할 수 있습니다.
실무에서 마주한 현실
개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.
1. 파일 연산 기본
기본 설정
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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;
}
파일 복사
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 기본 복사
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);
파일 이동 및 삭제
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 이름 변경 (이동)
fs::rename("old.txt", "new.txt");
// 파일 삭제
fs::remove("file.txt");
// 디렉토리 삭제 (재귀)
fs::remove_all("dir");
2. 디렉토리 연산
디렉토리 생성
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 단일 디렉토리
fs::create_directory("mydir");
// 중첩 디렉토리 (재귀)
fs::create_directories("path/to/dir");
// 이미 존재하면 false 반환
bool created = fs::create_directory("existing_dir");
if (!created) {
std::cout << "이미 존재하거나 생성 실패" << std::endl;
}
디렉토리 순회
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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. 복사 옵션
copy_options 플래그
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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);
copy_options 조합: 다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 재귀 + 덮어쓰기
fs::copy("src_dir", "dst_dir",
fs::copy_options::recursive |
fs::copy_options::overwrite_existing);
4. 실전 예제
예제 1: 안전한 파일 복사
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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: 디렉토리 백업
#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: 오래된 파일 정리
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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: 파일 동기화
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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. 자주 발생하는 문제
문제 1: 덮어쓰기 에러
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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: 디렉토리 삭제 실패
아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 비어있지 않은 디렉토리
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 복사
아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 이동 (빠름, 원자적)
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: 예외 처리
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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. 원자적 연산
rename의 원자성
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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;
}
비원자적 복사
다음은 cpp를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 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;
}
}
원자적 이동 패턴: 다음은 cpp를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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. 실전 예제: 파일 관리자
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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;
}
정리
핵심 요약
- 복사:
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버전으로 예외 없는 코드 작성- 로깅으로 작업 내역 기록
- 트랜잭션 패턴으로 롤백 가능하게 설계