библиотека — файловая системаlibrary — filesystem

std::filesystem

Портируемая работа с путями, файлами и директориями — стандартизация boost::filesystem, конец платформенных #ifdef и WinAPI/POSIX-обвязок вручную.Portable work with paths, files, and directories — the standardization of boost::filesystem, the end of platform #ifdefs and hand-rolled WinAPI/POSIX wrappers.

часто используетсяcommonly used частый вопрос: exceptions vs error_codecommon question: exceptions vs error_code

Что этоWhat it is

Пространство имён std::filesystem (заголовок <filesystem>, в коде почти всегда заводят алиас namespace fs = std::filesystem;) — набор типов и функций для работы с путями и файловой системой без привязки к платформе. Центральный тип — fs::path: представление пути, которое одинаково понимает '/' и '\', умеет склеиваться оператором / и разбираться на компоненты (stem(), extension(), parent_path()). Обход директорий даёт directory_iterator (один уровень) и recursive_directory_iterator (рекурсивно, включая поддиректории). Свободные функции покрывают типичные операции: exists(), is_directory(), is_regular_file(), file_size(), create_directory(), create_directories(), remove(), remove_all(), copy(), rename().The std::filesystem namespace (header <filesystem>, code almost always sets up an alias namespace fs = std::filesystem;) is a set of types and functions for working with paths and the filesystem without tying code to a platform. The central type is fs::path: a path representation that understands both '/' and '\' equally, can be joined with the / operator, and can be broken into components (stem(), extension(), parent_path()). Directory traversal is provided by directory_iterator (one level) and recursive_directory_iterator (recursive, including subdirectories). Free functions cover the typical operations: exists(), is_directory(), is_regular_file(), file_size(), create_directory(), create_directories(), remove(), remove_all(), copy(), rename().

до C++17 — платформенный API или boostbefore C++17 — platform API or boost
#ifdef _WIN32
#include <windows.h>
bool dirExists(const std::string& p) {
    auto attr = GetFileAttributesA(p.c_str());
    return attr != INVALID_FILE_ATTRIBUTES
        && (attr & FILE_ATTRIBUTE_DIRECTORY);
}
#else
#include <sys/stat.h>
bool dirExists(const std::string& p) {
    struct stat st;
    return stat(p.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
}
#endif
// либо то же самое, но через внешнюю зависимость boost::filesystem// or the same thing via the external boost::filesystem dependency
C++17 — std::filesystemC++17 — std::filesystem
#include <filesystem>
namespace fs = std::filesystem;

bool dirExists(const fs::path& p) {
    return fs::is_directory(p);  // одна строка, любая платформа// one line, any platform
}

Зачем ввелиWhy it was introduced

Работа с файловой системой оставалась одной из немногих повседневных задач, у которых в стандартной библиотеке вообще не было решения:Working with the filesystem remained one of the few everyday tasks that had no solution in the standard library at all:

01

boost::filesystem был прямым источником прототипаboost::filesystem was the direct source of the prototype

std::filesystem — это, по сути, стандартизация boost::filesystem: имена типов, семантика path, набор свободных функций почти дословно повторяют то, что годами уже было в boost и активно использовалось в промышленном коде. Комитет не изобретал новый дизайн — он кодифицировал уже проверенное практикой решение.std::filesystem is, in essence, the standardization of boost::filesystem: the type names, the semantics of path, and the set of free functions almost verbatim repeat what had already existed in boost for years and was actively used in production code. The committee didn't invent a new design — it codified a solution already proven in practice.

02

Портируемость без внешней зависимостиPortability without an external dependency

До C++17 портируемая работа с файлами требовала либо boost как зависимости всего проекта ради одной библиотеки, либо ручных #ifdef-обвязок вокруг WinAPI и POSIX — оба пути добавляли трение: boost тянул за собой сборочную инфраструктуру, а самописные обвязки плодили платформенно-специфичные баги. std::filesystem даёт то же самое из коробки, без единой внешней зависимости.Before C++17, portable file work required either boost as a whole-project dependency just for one library, or hand-written #ifdef wrappers around WinAPI and POSIX — both paths added friction: boost dragged in build infrastructure, and hand-rolled wrappers bred platform-specific bugs. std::filesystem gives the same thing out of the box, with zero external dependencies.

03

Единое представление пути вместо строк с ручным разборомA unified path representation instead of strings parsed by hand

До fs::path путь обычно хранился как обычная строка, а склейка компонентов, разбор расширения или родительской директории делались вручную через поиск подстрок — код, который тонко ломался на Windows-путях с обратным слешем. path инкапсулирует эту логику один раз и правильно.Before fs::path, a path was typically stored as a plain string, and joining components, extracting the extension, or getting the parent directory were done by hand via substring search — code that broke subtly on Windows paths with backslashes. path encapsulates that logic once, correctly.

Как это работаетHow it works

Типичный код обхода директории и работы с путём выглядит так:Typical directory-traversal and path-manipulation code looks like this:

#include <filesystem>
namespace fs = std::filesystem;

fs::path dir = "/data/logs";
for (const auto& entry : fs::recursive_directory_iterator(dir)) {
    if (entry.is_regular_file() && entry.path().extension() == ".log") {
        std::cout << entry.path() << " — "
                  << fs::file_size(entry.path()) << " bytes\n";
    }
}

// склейка путей — оператор /, а не конкатенация строк:// joining paths — the / operator, not string concatenation:
fs::path full = dir / "2026-07-27" / "app.log";
fs::create_directories(full.parent_path());  // создаёт все недостающие директории цепочкой// creates every missing directory in the chain

Ключевая развилка API — обработка ошибок. Почти каждая функция существует в двух вариантах: без последнего аргумента она бросает исключение fs::filesystem_error при сбое, а с последним аргументом std::error_code& — вместо исключения просто заполняет этот код и возвращает значение по умолчанию:The key API fork is error handling. Almost every function exists in two forms: without a trailing argument it throws an fs::filesystem_error exception on failure, and with a trailing std::error_code& argument it fills that code instead of throwing and returns a default value:

// стиль 1 — исключения:// style 1 — exceptions:
try {
    fs::remove("/tmp/maybe-missing.txt");
} catch (const fs::filesystem_error& e) {
    std::cerr << e.what() << '\n';
}

// стиль 2 — error_code, без throw:// style 2 — error_code, no throw:
std::error_code ec;
fs::remove("/tmp/maybe-missing.txt", ec);
if (ec) { std::cerr << ec.message() << '\n'; }

ГраблиGotchas

Смешение стиля исключений и error_code в одном месте сбивает с толкуMixing the exception style and the error_code style in one place is confusing

По умолчанию большинство операций кидают исключения — это легко забыть, если в соседней строке та же операция вызвана с error_code& и молча не бросает ничего. Стоит явно выбрать один стиль обработки ошибок для конкретного модуля или слоя кода и не смешивать оба варианта в одной функции — иначе легко получить необработанное исключение там, где ожидался просто ненулевой error_code.By default most operations throw exceptions — easy to forget if the same operation is called with an error_code& on the next line and silently throws nothing. It's worth explicitly picking one error-handling style for a given module or code layer and not mixing both within the same function — otherwise it's easy to end up with an unhandled exception where a nonzero error_code was expected instead.

operator== сравнивает пути лексикографически, не проверяет, один ли это файлoperator== compares paths lexicographically, not whether they're the same file

path хранится и сравнивается как последовательность компонентов — fs::path("/data/./log") == fs::path("/data/log") даст false, хотя оба пути указывают на один и тот же файл (через ., симлинк или относительный путь). Чтобы проверить, что два пути ведут к одному файлу на диске, нужен fs::equivalent(p1, p2), который действительно резолвит пути и сравнивает файлы, а не строки компонентов.path is stored and compared as a sequence of components — fs::path("/data/./log") == fs::path("/data/log") yields false, even though both paths point at the same file (via a ., a symlink, or a relative path). To check that two paths lead to the same file on disk, use fs::equivalent(p1, p2), which actually resolves the paths and compares the files, not the component strings.

Операции с файловой системой не атомарны и подвержены TOCTOUFilesystem operations aren't atomic and are subject to TOCTOU

Между проверкой fs::exists(p) и последующим открытием файла файл может быть удалён или заменён другим процессом — классическая гонка time-of-check-to-time-of-use. std::filesystem не решает эту проблему: если корректность зависит от атомарности, нужно опираться на коды ошибок самой операции открытия/создания файла, а не на предварительную проверку.Between checking fs::exists(p) and subsequently opening the file, the file can be removed or replaced by another process — the classic time-of-check-to-time-of-use race. std::filesystem doesn't solve this: if correctness depends on atomicity, rely on the error codes of the actual open/create operation itself, not on a preceding check.