Каким образом в C++/Linux можно создать файл, только если его не существует (атомарно)?

Ответ

Для атомарного создания файла только при его отсутствии (чтобы избежать состояния гонки между проверкой exists и созданием) необходимо использовать низкоуровневые системные вызовы или флаги файловой системы.

1. Использование системного вызова open() с флагами O_CREAT | O_EXCL (POSIX, наиболее надёжно): Этот метод атомарен даже в многопоточной и многопроцессной среде.

#include <fcntl.h>
#include <unistd.h>
#include <cerrno>
#include <iostream>

int fd = open("myfile.txt", O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd == -1) {
    if (errno == EEXIST) {
        std::cout << "Файл уже существует.n";
    } else {
        perror("open failed");
    }
} else {
    std::cout << "Файл успешно создан.n";
    close(fd); // Не забываем закрыть дескриптор
}

2. Использование std::ofstream с флагами (C++17, но не полностью атомарно на всех файловых системах):

#include <fstream>
#include <iostream>

std::ofstream file("myfile.txt", std::ios::out | std::ios::app); // app не перезаписывает
// Но это не гарантирует, что файл не существовал до этого.
// Более точный, но всё же не атомарный способ (C++17):
#include <filesystem>
namespace fs = std::filesystem;
if (!fs::exists("myfile.txt")) {
    std::ofstream new_file("myfile.txt");
}
// !ВНИМАНИЕ: Между проверкой exists и созданием файла другой процесс может успеть его создать.

Вывод: Для гарантированно атомарного создания в конкурентной среде следует использовать системный вызов open() с флагами O_CREAT | O_EXCL.

Ответ 18+ 🔞

А, слушай, тут такая тема интересная подъехала — как файл создать, чтобы не было этой ебалы с гонками, когда один процесс проверяет, существует ли файл, а второй уже успевает его создать в этот момент. Это же классика, ёпта! Представь: ты такой проверяешь if (!exists()), а в это время какой-то пидарас шерстяной уже успевает свой файл запилить, и твоя программа потом создаёт второй, перезаписывает или вообще пиздец какой-то. Удивление пиздец, когда потом разбираешься, откуда баги лезут.

Так вот, самый надёжный способ, чтобы атомарно создать файл только если его нет — это использовать низкоуровневый системный вызов open() с флагами O_CREAT | O_EXCL. Это, блядь, как волшебство: система сама проверяет существование и создаёт за одну операцию, без промежутков, где можно влезть. Даже если два процесса одновременно вызовут — один получит файл, а второй ошибку EEXIST. Красота, ядрёна вошь!

Вот смотри, как это выглядит в коде:

#include <fcntl.h>
#include <unistd.h>
#include <cerrno>
#include <iostream>

int fd = open("myfile.txt", O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd == -1) {
    if (errno == EEXIST) {
        std::cout << "Файл уже существует.n";
    } else {
        perror("open failed");
    }
} else {
    std::cout << "Файл успешно создан.n";
    close(fd); // Не забываем закрыть дескриптор
}

Видишь? Всё чётко. Создалось — ок. Не создалось, потому что уже есть — получишь ошибку и обработаешь. Никаких промежуточных состояний, где можно проёбаться.

А теперь, бля, смотри на то, что многие пытаются делать на C++ через std::ofstream или std::filesystem. Выглядит вроде прилично, но доверия ебать ноль в конкурентной среде!

#include <fstream>
#include <iostream>

std::ofstream file("myfile.txt", std::ios::out | std::ios::app); // app не перезаписывает
// Но это не гарантирует, что файл не существовал до этого.
// Более точный, но всё же не атомарный способ (C++17):
#include <filesystem>
namespace fs = std::filesystem;
if (!fs::exists("myfile.txt")) {
    std::ofstream new_file("myfile.txt");
}
// !ВНИМАНИЕ: Между проверкой exists и созданием файла другой процесс может успеть его создать.

Вот видишь этот комментарий? Он не просто так стоит. Между fs::exists и созданием файла — целая вечность в масштабах процессора! Какой-нибудь другой поток или процесс может в этот момент спокойно файл создать, и ты потом получишь два файла или конфликт. Это как раз та самая хитрая жопа, которая потом заставляет ночами дебажить.

Так что вывод, чувак, простой: если тебе нужна гарантия в многопоточной или многопроцессной среде — используй open() с O_CREAT | O_EXCL. Это старый, добрый, проверенный способ, который работает на уровне ядра, а не в пользовательском пространстве с его костылями. Всё остальное — это игра в русскую рулетку, только вместо пули — баг на проде.