C++17의 std::filesystem으로 크로스 플랫폼 파일 입출력 처리하기

C++17의 std::filesystem 라이브러리는 파일 및 디렉터리 조작을 위한 강력한 기능을 제공합니다. 이 라이브러리는 플랫폼에 독립적인 파일 시스템 접근을 지원하며, 복잡한 경로 변환, 파일 속성 조회, 디렉터리 탐색 등의 작업을 쉽게 수행할 수 있도록 설계되었습니다.

전통적으로 C++에서 파일 조작은 stdio.h의 C 표준 함수나 <fstream>을 사용하여 이루어졌지만, 이러한 방법은 플랫폼별 차이를 처리하기 어렵고, 파일 시스템 작업을 직관적으로 표현하기 어려웠습니다. std::filesystem은 이러한 문제를 해결하고 보다 현대적인 방식으로 파일 및 디렉터리를 조작할 수 있도록 돕습니다.

본 기사에서는 std::filesystem을 활용하여 파일 및 디렉터리를 다루는 다양한 방법을 소개하며, 크로스 플랫폼 개발에서 활용할 수 있는 실용적인 예제들을 제공할 것입니다.

std::filesystem 개요

C++17에 도입된 std::filesystem은 파일 시스템을 조작하기 위한 표준 라이브러리입니다. 이 라이브러리는 플랫폼 독립적인 방식으로 파일과 디렉터리를 관리할 수 있도록 지원하며, 복잡한 경로 처리를 간소화하는 기능을 제공합니다.

std::filesystem의 주요 기능


std::filesystem을 활용하면 다음과 같은 파일 시스템 작업을 수행할 수 있습니다.

  • 파일 및 디렉터리 조작: 생성, 삭제, 이동, 복사 등
  • 파일 속성 조회: 크기, 마지막 수정 시간, 권한 정보 확인
  • 디렉터리 탐색: 특정 경로 내 파일 목록 조회 및 필터링
  • 경로(path) 변환: OS별 파일 경로 차이 해결

헤더 파일 및 네임스페이스


std::filesystem을 사용하려면 <filesystem> 헤더를 포함해야 하며, C++의 std::filesystem 네임스페이스를 사용해야 합니다.

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path myPath{"example.txt"};
    std::cout << "파일 경로: " << myPath << std::endl;
    return 0;
}

위 코드에서 fs::path를 사용하여 파일 경로를 지정하고 출력할 수 있습니다.

플랫폼 독립적 파일 조작


기존에는 운영체제마다 다른 방식으로 파일 시스템을 다루어야 했지만, std::filesystem을 사용하면 Windows, Linux, macOS에서 동일한 코드로 파일 조작이 가능합니다.

이제 std::filesystem을 활용하여 실제로 파일 및 디렉터리를 조작하는 방법을 알아보겠습니다.

파일 및 디렉터리 조작 기본

C++17의 std::filesystem을 사용하면 간단한 코드로 파일 및 디렉터리를 조작할 수 있습니다. 파일 생성, 삭제, 이동, 복사 등의 작업을 수행하는 방법을 살펴보겠습니다.

파일 생성 및 삭제


파일을 생성하거나 삭제하는 가장 간단한 방법은 std::ofstreamstd::filesystem::remove를 사용하는 것입니다.

#include <iostream>
#include <filesystem>
#include <fstream>

namespace fs = std::filesystem;

int main() {
    std::string filename = "example.txt";

    // 파일 생성
    std::ofstream file(filename);
    if (file) {
        std::cout << filename << " 파일이 생성되었습니다." << std::endl;
    }
    file.close();

    // 파일 삭제
    if (fs::remove(filename)) {
        std::cout << filename << " 파일이 삭제되었습니다." << std::endl;
    } else {
        std::cout << filename << " 파일을 찾을 수 없습니다." << std::endl;
    }

    return 0;
}

위 코드에서는 std::ofstream을 이용해 파일을 생성한 후, fs::remove()를 사용하여 파일을 삭제합니다.

디렉터리 생성 및 삭제


디렉터리는 fs::create_directory()fs::remove_all()을 이용하여 생성 및 삭제할 수 있습니다.

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    std::string dirName = "test_directory";

    // 디렉터리 생성
    if (fs::create_directory(dirName)) {
        std::cout << dirName << " 디렉터리가 생성되었습니다." << std::endl;
    }

    // 디렉터리 삭제
    if (fs::remove_all(dirName)) {
        std::cout << dirName << " 디렉터리가 삭제되었습니다." << std::endl;
    }

    return 0;
}

위 코드에서 fs::create_directory()는 지정된 이름의 디렉터리를 생성하며, fs::remove_all()은 디렉터리와 그 안의 모든 파일을 삭제합니다.

파일 및 디렉터리 이동 및 복사


파일 및 디렉터리는 fs::rename()으로 이동할 수 있으며, fs::copy()를 사용해 복사할 수도 있습니다.

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    std::string srcFile = "source.txt";
    std::string destFile = "destination.txt";

    // 파일 이동 (이름 변경)
    fs::rename(srcFile, destFile);
    std::cout << srcFile << " → " << destFile << " 이동 완료" << std::endl;

    // 파일 복사
    fs::copy(destFile, "copy.txt");
    std::cout << destFile << " → copy.txt 복사 완료" << std::endl;

    return 0;
}

위 코드에서는 fs::rename()을 이용해 파일 이름을 변경하며, fs::copy()를 사용해 파일을 복사합니다.

디렉터리 복사


디렉터리를 복사하려면 fs::copy()fs::copy_options::recursive 옵션을 추가해야 합니다.

fs::copy("source_directory", "backup_directory", fs::copy_options::recursive);

이제 std::filesystem을 활용한 파일 및 디렉터리 조작 방법을 익혔으므로, 다음으로 경로(path) 처리를 살펴보겠습니다.

경로(path) 처리와 변환

C++17의 std::filesystem::path는 파일 및 디렉터리 경로를 다룰 때 유용한 클래스로, 운영체제에 따라 달라지는 경로 구분자를 자동으로 처리하고 다양한 변환 기능을 제공합니다.

경로 객체 생성 및 출력


std::filesystem::path 객체를 사용하면 파일 또는 디렉터리 경로를 쉽게 다룰 수 있습니다.

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath("C:/Users/Example/file.txt");

    std::cout << "파일 경로: " << filePath << std::endl;
    std::cout << "파일명: " << filePath.filename() << std::endl;
    std::cout << "확장자: " << filePath.extension() << std::endl;
    std::cout << "부모 디렉터리: " << filePath.parent_path() << std::endl;

    return 0;
}

위 코드에서는 filename(), extension(), parent_path() 등의 메서드를 사용하여 경로의 구성 요소를 추출할 수 있습니다.

경로 결합


경로를 결합할 때는 / 연산자를 사용하거나 append() 메서드를 활용할 수 있습니다.

fs::path dir = "C:/Users/Example";
fs::path fullPath = dir / "Documents" / "file.txt";

std::cout << "전체 경로: " << fullPath << std::endl;

또는 append()를 사용할 수도 있습니다.

fs::path base = "C:/Users";
base.append("Example").append("Documents").append("file.txt");

std::cout << "전체 경로: " << base << std::endl;

경로 변환


플랫폼에 따라 적절한 형식으로 경로를 변환하는 기능도 제공합니다.

fs::path unixPath("/home/user/file.txt");
fs::path winPath("C:\\Users\\file.txt");

std::cout << "유니코드 문자열 변환: " << unixPath.u8string() << std::endl;
std::cout << "네이티브 형식 변환: " << winPath.native() << std::endl;

u8string(), wstring(), native() 등의 메서드를 활용하면 UTF-8 문자열이나 네이티브 운영체제 형식으로 변환할 수 있습니다.

절대 경로 변환


상대 경로를 절대 경로로 변환하려면 absolute() 함수를 사용합니다.

fs::path relativePath("myfile.txt");
fs::path absolutePath = fs::absolute(relativePath);

std::cout << "절대 경로: " << absolutePath << std::endl;

경로 비교


경로가 같은지 비교할 때는 == 연산자를 사용하면 됩니다.

fs::path path1("C:/Users/Example/file.txt");
fs::path path2("C:/Users/Example/file.txt");

if (path1 == path2) {
    std::cout << "경로가 동일합니다." << std::endl;
}

이제 경로를 효과적으로 다룰 수 있는 방법을 배웠습니다. 다음으로 파일 속성 및 권한을 제어하는 방법을 알아보겠습니다.

파일 속성 및 권한 제어

C++17의 std::filesystem을 사용하면 파일 크기, 수정 시간, 권한 등을 쉽게 조회하고 변경할 수 있습니다. 이를 활용하면 프로그램에서 파일의 상태를 확인하거나, 보안 및 접근 권한을 조절할 수 있습니다.

파일 크기 확인


std::filesystem::file_size()를 사용하면 파일의 크기를 바이트 단위로 확인할 수 있습니다.

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "example.txt";

    if (fs::exists(filePath)) {
        std::cout << "파일 크기: " << fs::file_size(filePath) << " 바이트" << std::endl;
    } else {
        std::cout << "파일이 존재하지 않습니다." << std::endl;
    }

    return 0;
}

위 코드에서 fs::exists()로 파일의 존재 여부를 확인한 후 fs::file_size()를 사용하여 크기를 출력합니다.

파일의 마지막 수정 시간 조회


파일의 마지막 수정 시간을 가져오려면 last_write_time()을 사용합니다.

#include <iostream>
#include <filesystem>
#include <chrono>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "example.txt";

    if (fs::exists(filePath)) {
        auto ftime = fs::last_write_time(filePath);
        auto cftime = decltype(ftime)::clock::to_time_t(ftime);

        std::cout << "마지막 수정 시간: " << std::ctime(&cftime);
    } else {
        std::cout << "파일이 존재하지 않습니다." << std::endl;
    }

    return 0;
}

last_write_time()을 사용하면 파일의 수정 시간을 가져올 수 있으며, 이를 std::ctime()을 이용해 사람이 읽을 수 있는 형식으로 변환할 수 있습니다.

파일 권한 확인 및 변경


파일의 접근 권한을 확인하고 변경하는 데 fs::status()fs::permissions()를 사용할 수 있습니다.

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "example.txt";

    // 파일 권한 확인
    fs::perms p = fs::status(filePath).permissions();

    if ((p & fs::perms::owner_read) != fs::perms::none) {
        std::cout << "파일을 읽을 수 있습니다." << std::endl;
    }
    if ((p & fs::perms::owner_write) != fs::perms::none) {
        std::cout << "파일을 쓸 수 있습니다." << std::endl;
    }

    return 0;
}

위 코드에서는 fs::status(filePath).permissions()를 통해 파일 권한을 확인합니다.

파일 권한 변경


파일의 읽기 및 쓰기 권한을 변경하려면 fs::permissions() 함수를 사용합니다.

fs::permissions("example.txt", fs::perms::owner_read | fs::perms::owner_write);

위 코드는 파일 소유자가 읽기 및 쓰기 권한을 가지도록 설정합니다.

또한, 특정 권한을 추가하거나 제거할 수도 있습니다.

fs::permissions("example.txt", fs::perms::owner_write, fs::perm_options::remove);

이렇게 하면 파일의 쓰기 권한이 제거됩니다.

파일 유형 확인


파일이 일반 파일인지, 디렉터리인지 확인하려면 fs::is_regular_file()fs::is_directory()를 사용할 수 있습니다.

if (fs::is_regular_file("example.txt")) {
    std::cout << "일반 파일입니다." << std::endl;
}

if (fs::is_directory("example_directory")) {
    std::cout << "디렉터리입니다." << std::endl;
}

위 기능들을 활용하면 파일 속성을 손쉽게 조회하고 관리할 수 있습니다. 다음으로는 파일 탐색과 반복자를 활용하는 방법을 살펴보겠습니다.

파일 탐색 및 반복자 활용

C++17의 std::filesystem을 사용하면 디렉터리 내 파일을 효율적으로 탐색할 수 있습니다. 이를 통해 특정 확장자를 가진 파일을 검색하거나, 서브디렉터리를 포함한 전체 파일 목록을 가져올 수 있습니다.

디렉터리 내 파일 목록 출력


디렉터리 내 파일과 폴더를 나열하려면 std::filesystem::directory_iterator를 사용합니다.

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path dirPath = "example_directory";

    if (fs::exists(dirPath) && fs::is_directory(dirPath)) {
        std::cout << dirPath << " 내 파일 목록:" << std::endl;

        for (const auto& entry : fs::directory_iterator(dirPath)) {
            std::cout << entry.path() << std::endl;
        }
    } else {
        std::cout << "디렉터리가 존재하지 않습니다." << std::endl;
    }

    return 0;
}

위 코드는 지정한 디렉터리 내의 모든 파일과 하위 폴더를 출력합니다.

서브디렉터리 포함 전체 탐색


디렉터리 내부를 재귀적으로 탐색하려면 recursive_directory_iterator를 사용합니다.

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path dirPath = "example_directory";

    if (fs::exists(dirPath) && fs::is_directory(dirPath)) {
        std::cout << dirPath << " 내 모든 파일 및 폴더:" << std::endl;

        for (const auto& entry : fs::recursive_directory_iterator(dirPath)) {
            std::cout << entry.path() << std::endl;
        }
    } else {
        std::cout << "디렉터리가 존재하지 않습니다." << std::endl;
    }

    return 0;
}

위 코드는 디렉터리 내부의 모든 파일과 서브디렉터리를 포함하여 탐색하는 방법을 보여줍니다.

특정 확장자의 파일만 검색


특정 확장자를 가진 파일만 찾고 싶다면 .extension()을 활용할 수 있습니다.

fs::path dirPath = "example_directory";
std::string targetExt = ".txt";

for (const auto& entry : fs::directory_iterator(dirPath)) {
    if (entry.path().extension() == targetExt) {
        std::cout << "TXT 파일: " << entry.path() << std::endl;
    }
}

위 코드는 .txt 확장자를 가진 파일만 출력합니다.

파일 탐색 최적화

  • 대량 파일 처리: directory_iterator는 필요할 때만 파일을 로드하므로 성능이 좋음
  • 재귀 탐색 시 depth() 활용: 너무 깊은 탐색을 방지하려면 depth()를 활용
  • 대용량 데이터 검색: 대용량 폴더 탐색 시 멀티스레딩 고려 가능

이제 std::filesystem을 활용한 파일 탐색 방법을 익혔으므로, 다음으로 파일 스트림과 연계하는 방법을 살펴보겠습니다.

std::filesystem과 파일 스트림 연계

C++17의 std::filesystemstd::ifstream, std::ofstream과 함께 사용하면 파일을 더욱 직관적으로 다룰 수 있습니다. 이를 통해 파일의 존재 여부를 확인하고, 안전하게 파일을 읽고 쓸 수 있습니다.

파일 존재 여부 확인 후 열기


파일을 열기 전에 fs::exists()를 사용하여 존재 여부를 확인하면 파일 작업의 안전성을 높일 수 있습니다.

#include <iostream>
#include <fstream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "example.txt";

    if (fs::exists(filePath)) {
        std::ifstream file(filePath);
        std::cout << "파일이 존재하며, 열었습니다." << std::endl;
    } else {
        std::cout << "파일이 존재하지 않습니다." << std::endl;
    }

    return 0;
}

위 코드에서는 파일이 존재하는 경우에만 열도록 처리했습니다.

파일 생성 및 쓰기


파일을 생성하고 데이터를 기록하는 예제입니다.

#include <iostream>
#include <fstream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "output.txt";

    std::ofstream outFile(filePath);
    if (outFile) {
        outFile << "std::filesystem과 파일 스트림 연계 예제입니다." << std::endl;
        std::cout << "파일 생성 및 데이터 기록 완료!" << std::endl;
    } else {
        std::cout << "파일을 생성할 수 없습니다." << std::endl;
    }

    return 0;
}

위 코드에서는 std::ofstream을 사용하여 새 파일을 생성하고 데이터를 기록했습니다.

파일 읽기


파일의 내용을 읽어 출력하는 코드입니다.

#include <iostream>
#include <fstream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "output.txt";

    if (fs::exists(filePath)) {
        std::ifstream inFile(filePath);
        std::string line;

        while (std::getline(inFile, line)) {
            std::cout << line << std::endl;
        }
    } else {
        std::cout << "파일이 존재하지 않습니다." << std::endl;
    }

    return 0;
}

이 코드는 파일이 존재하면 내용을 한 줄씩 읽어 출력합니다.

파일 크기를 확인한 후 읽기


파일이 비어 있는지 확인한 후 읽는 방법입니다.

if (fs::exists(filePath) && fs::file_size(filePath) > 0) {
    std::ifstream inFile(filePath);
    std::string content((std::istreambuf_iterator<char>(inFile)), std::istreambuf_iterator<char>());
    std::cout << "파일 내용:\n" << content << std::endl;
} else {
    std::cout << "파일이 비어 있거나 존재하지 않습니다." << std::endl;
}

파일 크기가 0보다 클 때만 읽도록 하여 불필요한 처리를 방지합니다.

파일 스트림과 경로 변환


std::filesystem::path 객체를 std::ofstreamstd::ifstream에 직접 전달할 수 있습니다.

fs::path logFile = "logs/app.log";
std::ofstream logStream(logFile, std::ios::app);
logStream << "로그 기록: 프로그램 실행됨." << std::endl;

이 방식은 운영체제의 파일 경로 형식과 무관하게 동작합니다.

파일 스트림과 std::filesystem의 장점

  • std::filesystem을 사용하면 파일 존재 여부를 사전에 확인하여 오류를 방지할 수 있음
  • std::path를 활용하면 운영체제에 따라 경로 형식을 일일이 맞출 필요가 없음
  • file_size()와 함께 활용하면 대용량 파일을 효율적으로 처리할 수 있음

이제 파일 스트림과 std::filesystem을 연계하는 방법을 익혔습니다. 다음으로는 예외 처리 및 오류 대응 방법을 살펴보겠습니다.

예외 처리 및 오류 대응

C++17의 std::filesystem을 사용할 때 파일이 존재하지 않거나, 읽기/쓰기 권한이 없거나, 디스크 공간이 부족한 경우 예외가 발생할 수 있습니다. 이러한 오류를 안전하게 처리하려면 try-catch 블록과 std::error_code를 활용해야 합니다.

예외 발생 시 잡아내기


std::filesystem의 일부 함수는 예외를 던지므로, 반드시 try-catch로 감싸서 처리해야 합니다.

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    try {
        fs::path invalidPath = "invalid_directory";

        // 존재하지 않는 디렉터리를 삭제하려고 하면 예외 발생
        fs::remove(invalidPath);

        std::cout << "디렉터리가 삭제되었습니다." << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cerr << "파일 시스템 오류 발생: " << e.what() << std::endl;
    }

    return 0;
}

위 코드에서 존재하지 않는 디렉터리를 삭제하려 하면 예외가 발생하며, 이를 catch 블록에서 잡아 메시지를 출력합니다.

std::error_code를 활용한 안전한 오류 처리


예외가 발생하지 않도록 std::error_code를 사용하면 보다 안전하게 오류를 확인할 수 있습니다.

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path dirPath = "example_directory";
    std::error_code ec;

    // 디렉터리 생성 시 오류 코드 사용
    if (!fs::create_directory(dirPath, ec)) {
        std::cerr << "디렉터리 생성 실패: " << ec.message() << std::endl;
    } else {
        std::cout << "디렉터리 생성 완료!" << std::endl;
    }

    return 0;
}

이 코드에서는 fs::create_directory()를 실행할 때 예외를 발생시키지 않고 std::error_code를 통해 오류 여부를 확인합니다.

파일 열기 시 오류 처리


파일을 열 때도 std::filesystem을 활용하여 안전하게 오류를 처리할 수 있습니다.

#include <iostream>
#include <fstream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = "readonly.txt";

    std::ifstream inFile(filePath);
    if (!inFile) {
        std::cerr << "파일을 열 수 없습니다: " << filePath << std::endl;
        return 1;
    }

    std::cout << "파일을 성공적으로 열었습니다." << std::endl;
    return 0;
}

위 코드에서는 파일이 존재하지 않거나 읽기 권한이 없을 경우 오류 메시지를 출력합니다.

파일 복사 시 오류 처리


파일을 복사하는 경우 파일이 존재하지 않거나 대상 파일이 이미 존재할 경우 오류가 발생할 수 있습니다. 이를 방지하려면 std::error_code를 사용합니다.

fs::path src = "source.txt";
fs::path dest = "destination.txt";
std::error_code ec;

fs::copy(src, dest, fs::copy_options::skip_existing, ec);

if (ec) {
    std::cerr << "파일 복사 실패: " << ec.message() << std::endl;
} else {
    std::cout << "파일 복사 성공!" << std::endl;
}

이렇게 하면 대상 파일이 이미 존재할 경우 오류를 방지하고, 필요에 따라 다른 복사 옵션을 설정할 수 있습니다.

파일 시스템 오류를 효과적으로 처리하는 방법

  1. 가능한 예외를 예상하고 try-catch 블록을 사용
  2. 오류 코드(std::error_code)를 활용하여 예외 발생을 피함
  3. 파일 및 디렉터리 존재 여부를 사전에 확인 (fs::exists() 활용)
  4. 권한 문제를 사전에 검사 (fs::status()fs::perms 활용)

이제 std::filesystem을 사용할 때 발생할 수 있는 예외를 효과적으로 처리하는 방법을 익혔습니다. 다음으로는 크로스 플랫폼 호환성을 고려하여 파일 시스템을 다루는 방법을 살펴보겠습니다.

크로스 플랫폼 호환성 고려 사항

C++17의 std::filesystem은 Windows, Linux, macOS에서 동일한 API를 제공하지만, 운영체제별 파일 시스템 차이로 인해 호환성 문제가 발생할 수 있습니다. 본 섹션에서는 크로스 플랫폼 파일 시스템 프로그래밍 시 고려해야 할 주요 사항을 다룹니다.

경로 구분자 차이


운영체제마다 경로 구분자가 다릅니다.

  • Windows: C:\Users\Example\file.txt (백슬래시 \ 사용)
  • Linux/macOS: /home/example/file.txt (슬래시 / 사용)

std::filesystem::path를 사용하면 OS에 맞게 자동 변환됩니다.

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path filePath = fs::path("C:/Users/Example/file.txt");  // OS에 맞게 변환됨
    std::cout << "네이티브 경로: " << filePath.native() << std::endl;

    return 0;
}

따라서 경로를 문자열로 직접 지정하는 것보다 fs::path 객체를 사용하는 것이 좋습니다.

파일 경로 인코딩 차이


Windows에서는 기본적으로 UTF-16, Linux/macOS는 UTF-8을 사용합니다.

Windows에서 유니코드 경로 지원

fs::path winPath = L"C:\\사용자\\문서\\파일.txt";  // 유니코드 경로
std::wcout << L"경로: " << winPath << std::endl;

Windows에서 경로를 다룰 때는 wchar_t를 사용해야 합니다.

Linux/macOS에서 UTF-8 문자열 사용

fs::path unixPath = u8"/home/사용자/문서/파일.txt";
std::cout << "경로: " << unixPath.u8string() << std::endl;

UTF-8 문자열을 사용하면 멀티바이트 문자(예: 한글) 경로를 처리할 수 있습니다.

파일 권한 차이


Linux/macOS는 POSIX 권한 모델을 사용하지만, Windows는 ACL(Access Control List) 기반입니다.

파일 권한을 확인하려면 다음을 사용합니다.

fs::perms permissions = fs::status("example.txt").permissions();
if ((permissions & fs::perms::owner_read) != fs::perms::none) {
    std::cout << "읽기 가능" << std::endl;
}

하지만 Windows에서는 권한이 다르게 해석될 수 있습니다. 따라서 Windows의 파일 권한을 변경하려면 SetFileSecurity() 같은 API를 사용해야 합니다.

파일 속성 조회 차이


Windows에서는 숨김 파일(hidden attribute)을 사용하며, Linux/macOS에서는 파일명이 .으로 시작하면 숨김 파일로 간주됩니다.

Windows에서 파일이 숨김인지 확인하는 방법:

#ifdef _WIN32
#include <windows.h>
bool isHidden(const fs::path& path) {
    DWORD attrs = GetFileAttributesW(path.c_str());
    return (attrs != INVALID_FILE_ATTRIBUTES) && (attrs & FILE_ATTRIBUTE_HIDDEN);
}
#endif

Linux/macOS에서 파일이 숨김인지 확인하는 방법:

bool isHidden(const fs::path& path) {
    return path.filename().string().front() == '.';
}

파일 시스템 유형 확인


운영체제별로 파일 시스템이 다르므로, 특정 기능(예: 심볼릭 링크, 압축 속성 등)이 지원되지 않을 수 있습니다.

fs::space_info diskInfo = fs::space("/");
std::cout << "전체 용량: " << diskInfo.capacity << " 바이트" << std::endl;
std::cout << "사용 가능 용량: " << diskInfo.available << " 바이트" << std::endl;

위 코드는 리눅스와 macOS에서 사용 가능하지만, Windows에서는 드라이브 문자를 지정해야 합니다.

크로스 플랫폼 개발 시 고려할 사항

  1. 경로 처리는 항상 std::filesystem::path를 사용할 것
  2. 파일 경로의 인코딩 차이를 고려할 것 (UTF-8 vs UTF-16)
  3. 권한 관리는 OS별로 다르게 처리해야 함
  4. 숨김 파일 및 속성 차이를 반영할 것
  5. 파일 시스템 유형에 따라 다른 동작을 할 수 있도록 대비할 것

이제 std::filesystem을 활용하여 크로스 플랫폼에서 호환성을 유지하는 방법을 배웠습니다. 다음으로 전체 내용을 정리하는 요약 섹션을 살펴보겠습니다.

요약

본 기사에서는 C++17의 std::filesystem을 활용하여 크로스 플랫폼에서 파일과 디렉터리를 효과적으로 조작하는 방법을 살펴보았습니다.

  1. 기본 개념: std::filesystem을 사용하면 파일 및 디렉터리 작업을 플랫폼 독립적으로 수행할 수 있습니다.
  2. 파일 및 디렉터리 조작: 생성, 삭제, 이동, 복사 등의 작업을 쉽게 처리할 수 있습니다.
  3. 경로(path) 처리: 경로 변환, 절대/상대 경로 변환, 경로 결합 등을 지원합니다.
  4. 파일 속성 및 권한 제어: 파일 크기, 수정 시간, 권한을 확인하고 변경할 수 있습니다.
  5. 파일 탐색: directory_iteratorrecursive_directory_iterator를 활용하여 파일 목록을 검색할 수 있습니다.
  6. 파일 스트림 연계: std::ifstreamstd::ofstreamstd::filesystem을 결합하여 안전한 파일 입출력을 구현할 수 있습니다.
  7. 예외 처리 및 오류 대응: try-catchstd::error_code를 활용하여 안전한 파일 시스템 작업을 수행할 수 있습니다.
  8. 크로스 플랫폼 호환성: 운영체제별 차이점을 고려하여 코드의 이식성을 높일 수 있습니다.

이제 std::filesystem을 활용하여 파일 시스템 작업을 더욱 효율적으로 수행할 수 있습니다. 이를 통해 C++ 기반 애플리케이션에서 파일 및 디렉터리 관리를 손쉽게 구현할 수 있을 것입니다.