Дан код на современном C++ с RAII: class File{ FILE* f; public: File(const char* path){ f=fopen(path,"r"); } ~File(){ if(f) fclose(f); } void write(const std::string& data){ /* может бросать исключение */ } }; Объясните возможные проблемы с безопасностью при исключениях и правильный способ управления ресурсами и ошибками в современных C++ (RAII, smart pointers, std::fstream)

2 Июл в 10:19
40 +1
0
Ответы
1
Проблемы в приведённом коде
- Нельзя игнорировать результат открытия: в конструкторе не проверяется `f=fopen(...)`. При неудаче `f==nullptr` — дальнейшие вызовы приводят к UB или бросанию исключений.
- У класса есть неуправляемый ресурс (`FILE*`), но сгенерированные копирующие операции копируют указатель — двойное `fclose` при копировании (double free/use-after-free).
- Деструктор не должен бросать исключения. Если `write()` бросит во время распаковки исключения, а деструктор выполнит код, который может бросить — программа завершится (terminate).
- Метод `write()` должен корректно обрабатывать ошибки ввода/вывода и обеспечивать согласованное поведение при исключениях (атомарность/сильная гарантия, если требуется).
- В целом использование "сырых" указателей усложняет безопасную работу при исключениях; лучше пользоваться RAII-обёртками/стандартными средствами.
Правильный подход в современном C++
1) RAII + умные указатели (для `FILE*`)
- Заворачивайте `FILE*` в владеющий объект — например `std::unique_ptr` с пользовательским удалятором. Конструктор должен проверять `fopen` и при неудаче бросать исключение или возвращать ошибку. Копирование запрещено, перемещение разрешено.
Пример:
```
#include #include #include #include #include #include
class File {
std::unique_ptr f_{nullptr, &fclose};
public:
explicit File(const std::filesystem::path& p) {
FILE* raw = std::fopen(p.string().c_str(), "r");
if (!raw) throw std::system_error(errno, std::generic_category(), "fopen failed");
f_.reset(raw);
}
File(const File&) = delete;
File& operator=(const File&) = delete;
File(File&&) noexcept = default;
File& operator=(File&&) noexcept = default;
void write(const std::string& data) {
if (!f_) throw std::runtime_error("file not open");
size_t written = std::fwrite(data.data(), 1, data.size(), f_.get());
if (written != data.size()) throw std::runtime_error("write failed");
}
};
```
Плюсы: безопасное освобождение ресурса, перемещаемость, деструктор не бросает исключений.
2) Предпочитать std::fstream для файловых операций
- Стандартные потоки уже RAII-совместимы и интегрированы с iostream-исключениями/флагами состояния. Можно включить исключения для fail/bad и работать в исключительной семантике.
Пример:
```
#include #include #include
class FileStream {
std::ofstream ofs_;
public:
explicit FileStream(const std::filesystem::path& p) {
ofs_.exceptions(std::ofstream::failbit | std::ofstream::badbit);
ofs_.open(p, std::ios::binary | std::ios::out);
}
FileStream(const FileStream&) = delete;
FileStream& operator=(const FileStream&) = delete;
FileStream(FileStream&&) = default;
FileStream& operator=(FileStream&&) = default;
void write(const std::string& data) {
ofs_.write(data.data(), data.size()); // при ошибке будет выброшено std::ios_base::failure
}
};
```
Плюсы: меньше ручной проверки ошибок, совместимость со стандартными механизмами.
3) Политика обработки ошибок — выбирайте и документируйте
- Либо бросать исключения (std::system_error, std::runtime_error, std::ios_base::failure), либо возвращать ошибки через `std::error_code`/возврат значения/`std::optional`/`expected` (в зависимости от требований).
- Внутри класса соблюдайте единый стиль: конструктор может бросать при невозможности открыть файл; методы чтения/записи — бросают при ошибке или возвращают код ошибки.
Ключевые рекомендации кратко
- Не храните "сырые" владеющие указатели; используйте RAII (std::unique_ptr с делетером) или std::fstream.
- Удаляйте копирование (delete copy ctor/assign), предоставляйте перемещение (move).
- Деструктор должен быть noexcept (не выполнять бросающих операций).
- Проверяйте результат открытия и операций I/O, согласованно передавайте ошибки наружу (исключения или error_code).
- Для путей используйте std::filesystem::path.
Это обеспечивает безопасность при исключениях, предотвращает двойное освобождение ресурсов и делает код проще для поддержки.
2 Июл в 10:26
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир