Как быстро проверить существование файла в C++ (C++11/14/17)?
#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 из первого ответа.