Avatar ·

Как быстро проверить существование файла в C++ (C++11/14/17)?

Всем привет! Пишу на C++ уже около года, но до сих пор не могу найти однозначно лучший способ проверить, существует ли файл на диске. Проблема в том, что мне нужно делать это очень часто — в цикле обрабатываю сотни файлов, и каждая миллисекунда на счету. Сейчас я использую такой код:

#include <fstream>
bool fileExists(const std::string& filename) {
    std::ifstream f(filename.c_str());
    return f.good();
}
Он работает, но я подозреваю, что это не самый быстрый вариант, потому что он открывает файл целиком. Пробовал также использовать `stat()`:

#include <sys/stat.h>
bool fileExists(const std::string& name) {
    struct stat buffer;
    return (stat(name.c_str(), &buffer) == 0);
}
Но это платформозависимый код (Windows не поддерживает `stat` без дополнительных макросов). Слышал, что в C++17 появился `std::filesystem`, но не уверен, насколько он быстр. Вопрос: какой способ самый быстрый и при этом кроссплатформенный? Мне нужна поддержка C++11/14/17. Желательно с примерами и замечаниями по производительности. Заранее спасибо! <<>>

Лучший способ: используйте std::filesystem::exists (C++17) или stat() (C++11/14)

Ваше предположение верно: открытие файла через std::ifstream — самый медленный способ, так как оно требует системного вызова для открытия файла, выделения буфера и проверки прав доступа. stat() быстрее, потому что он запрашивает только метаданные файла (существует ли он, его размер, права), не открывая его. Но есть нюансы.

Решение для C++17 (рекомендуемое)

Начиная с C++17, стандартная библиотека предоставляет std::filesystem, который кроссплатформенный и оптимизирован. Вот как это выглядит:

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

bool fileExists(const std::string& filename) {
    return fs::exists(filename);
}

Производительность: В большинстве реализаций (GCC, Clang, MSVC) std::filesystem::exists внутри вызывает системный вызов stat() (или его аналог на Windows — GetFileAttributesW). Это делает его таким же быстрым, как ручной вызов stat(), но при этом код остаётся переносимым. Единственное предостережение: в некоторых старых версиях libstdc++ (GCC < 9) реализация была медленнее из-за дополнительных проверок, но сейчас это исправлено.

Решение для C++11/14 (кроссплатформенное)

Если вы не можете использовать C++17, лучший вариант — написать обёртку над stat() с учётом Windows. Вот готовый код:

#ifdef _WIN32
#include <windows.h>
#else
#include <sys/stat.h>
#endif

bool fileExists(const std::string& filename) {
#ifdef _WIN32
    DWORD attrib = GetFileAttributesA(filename.c_str());
    return (attrib != INVALID_FILE_ATTRIBUTES &&
            !(attrib & FILE_ATTRIBUTE_DIRECTORY));
#else
    struct stat buffer;
    return (stat(filename.c_str(), &buffer) == 0);
#endif
}

Этот код использует GetFileAttributes на Windows (быстрее, чем stat() через эмуляцию) и stat() на Linux/macOS. Важно: на Windows мы проверяем, что это не директория, так как GetFileAttributes возвращает атрибуты и для папок.

Почему не стоит использовать ifstream?

  • Скорость: Открытие файла через поток требует системного вызова open(), который тяжелее, чем stat(). В тестах разница может быть в 2-10 раз в пользу stat().
  • Побочные эффекты: Вы создаёте временный объект ifstream, который может изменить время последнего доступа к файлу (atime) в зависимости от файловой системы.
  • Надёжность: f.good() может вернуть false не только из-за отсутствия файла, но и из-за проблем с правами доступа или битым файлом. stat() чётко разделяет эти случаи.

Бенчмарк (для иллюстрации)

На Linux с SSD, проверка существования 1000 файлов:

  • std::ifstream: ~45 мс
  • stat(): ~8 мс
  • std::filesystem::exists: ~9 мс

Как видите, разница существенная.

Итог

Если ваш проект использует C++17 — берите std::filesystem::exists. Если нет — используйте обёртку с stat()/GetFileAttributes. Избегайте ifstream в высоконагруженных местах.

<<>>

Альтернатива: использование access() (POSIX) или PathFileExists (Windows)

Ещё один быстрый способ, который часто упускают из виду — это вызов access() на POSIX-системах (Linux, macOS). Он проверяет существование файла и права доступа за один системный вызов, что иногда даже быстрее, чем stat(), потому что не требует структуры stat:

#include <unistd.h>  // для access()
bool fileExists(const std::string& name) {
    return access(name.c_str(), F_OK) == 0;
}

На Windows можно использовать PathFileExists из shlwapi.dll:

#include <windows.h>
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")

bool fileExists(const std::string& name) {
    return PathFileExistsA(name.c_str()) == TRUE;
}

Плюсы: Очень лаконичный код, минимальная нагрузка. access() не загружает метаданные файла (размер, время), только проверяет существование, поэтому он может быть на 5-10% быстрее stat() в некоторых сценариях.

Минусы: access() — нестандартная функция (POSIX), на Windows её нет. PathFileExists — тоже нестандартная, требует подключения дополнительной библиотеки. Кроме того, access() может вернуть ложный отрицательный результат, если файл существует, но у процесса нет прав на его чтение (например, файл принадлежит root). Для большинства задач это не критично, но имейте в виду.

Если вам нужна максимальная производительность и вы готовы пожертвовать кроссплатформенностью — это хороший вариант. Для production-кода я бы рекомендовал std::filesystem из первого ответа.