Pull to refresh

Comments 63

UFO landed and left these words here
+ логика странная (зачем открывать файл и сразу же его закрывать) + ф-я возвращала bool, судя по всему, но потом была переделка на исключения. В общем, пример неудачен да ещё и с ошибками
Пример как раз удачен. Типичный результат tester-driven development — я уверен, что это всё закрывало какие-то баги в багтерекре.

А вся статья, как бы, описывает: как стоило бы решить ту же проблему нормально…
UFO landed and left these words here

не все, большинство делают запрос к ФС.


да и опять же, тут как с проверкой интернета, надо сразу делать то, что надо и корректно обрабатывать ошибки.

Открыть файл как раз самый медленный способ проверить его существование. Он отрабатывает быстро только после попадания файла в дисковый кеш.
нет. Нормальные оберкти внутри ничего такого не делают- файлы не открываете.
Под windows — используют FindFirstFileW() и stat() под unix
иначе вы расширяете проверку до «есть ли файл и можно ли его открыть текущему польвавателю»
Спасибо! Я был уж очень невнимателен, когда выделял код для публикации (позор, позор мне). В оригинале всё-таки был кастомный RAII FileHandle, который считал INVALID_HANDLE_VALUE за нулевое значение и корректно вызывал CloseHandle в остальных случаях. Я хотел убрать этот RAII чтобы не запутывать.
Исправил в статье.
А ничего, что вы выбросили смысл вашего кода и забыли про это? Если после того, как вы в else-блоке закрыли файл и вернули оси блокировку, произойдёт переключение контекста и ваш файл успешно потрёт другой поток, приложение свалится нафиг.
Я был невнимателен, когда готовил первый кусок кода к публикации. Спасибо.
UFO landed and left these words here

Теперь в ветке #else переменная err не инициализирована и при корректном file может содержать мусор, что превратится в фальшивую ошибку. Я не люблю функции с суффиксом "_s" как раз по этой причине: они создают видимость более безопасного решения, при этом зачастую даже провоцируют ошибки, как scanf_s. Ну и кроме того, это специфичный для компилятора код, введённый без особых причин. На использование wchar_t версии хотя бы есть причина — поддержка Unicode-путей.

UFO landed and left these words here

На самом деле, надо проверять не только _MSC_VER, но и __STDC_VERSION__ ...

Считается ли модным сейчас в C++ использовать исключения или коды возвраты?
UFO landed and left these words here

Если "пусть падает", то юзайте сразу terminate. Зачем в таком случае исключение бросать?

UFO landed and left these words here

Да, буду говорить если падать, то давайте оставим открытые сокеты, висячие коннекты, залоченные файлы, кучу мусора в папке temp. Что в этом плохого? Система всё почистит. Это в любом случае возможный исход работы программы. Представьте что будет если внезапно отключить питание.


Раскрутка стека нужна, только если программа будет продолжать работать. Если падаем, то раскручивать смысла нет.


Что такое "второй круг"?


Вы сказали исключение никто не ловит, а теперь у вас уже какие то логи.

UFO landed and left these words here
Вот в том и дело, что обычный рабочий момент приравнивается к внезапному отключению питания. Давайте будем взрывать системный блок, если не нашли своего раздела в реестре?

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

Мы говорим про "обычный рабочий момент" или про "дальше жить незачем"?


От того, что некая клетка организма умерла, вовсе не следует, что и весь организм должен за ней последовать. Но клетка для себя — умерла.

Неуместное сравнение.

UFO landed and left these words here
Не понимаю, чего вы от меня добиться хотите.

Хочу, что б вы признали, что исключения, которые никто не ловит, бессмысленны.


Но вы предлагаете еще и всю программу свалить, причем самым дурацким из возможных способом, не дав ей за собой почистить.

Может вы перестанете сыпать эпитетами и скажете, наконец, что конкретно не так с отсутсвием освобождения ресурсов при завершении программы?

UFO landed and left these words here
Исключения, которые никто не ловит, это единственный способ аварийно завершить программу, написанную на C++ (а не на C). Но я еще раз повторю, речь шла не об этом.

Ага, как же. terminate, exit, return -1 из main в конце концов. А о чём тогда?


Конкретно. Как будет закрыто TCP-соединение, если оно существовало в момент заверешения? Так оно должно закрываться? Как будет завершена сессия TLS поверх этого соединения? Так она должна завершаться? Что станет со встроенной базой данных на sqlite? Что сделается с криптоключами, созданными программой? Чем закончится асинхронная запись в файл? Даже попросту что произойдет с иконкой программы в трее?

Почаще задавайте себе такие вопросы. TCP-соединение отвалится по таймауту, соответственно, и tls сессия. С sqlite все будет отлично. Там транзакционноссть гарантирует целостность данных. Про ключи не понял. С иконкой, действительно, беда, но это в первую очередь демонстрирует кривость windows.


Стоит уточнить, что я не утверждаю, что исключения не нужны, что terminate можно использовать где угодно, и, конечно, что ресурсы не нужно освобождать перед завершением работы программы. Я хочу скзать, что в случаях, когда ошибку обработать невозможно, к примеру при инициализации глобальных объектов, нужно вызывать terminate, а не бросать какие-то исключения.

UFO landed and left these words here

Я не понял, что вы хотели сказать фразой


бросать исключение надо в ожидании, что его никто не ловит, то есть пусть падает, все равно уже «дальше жить незачем».

По-вашему, если в этом конструкторе произдёт ошибка, то исключение бросать не нужно что ли? Ерунда какая-то.

UFO landed and left these words here
UFO landed and left these words here
Убегающее за пределы main исключение — это UB
С какого перепугу? Вы про std::terminate вообще что-нибудь слышали?

Есть все шансы, что yay dtor вы не увидите.
Это почему ещё? Да, компилятор может «просечь», что в этой программе обязательно будет вызван деструктор, а за ним — std::terminate и вместо компиляции сложных таблиц и всего прочего просто напрямую вызвать Foo::~Foo() (хотя я таких компиляторов не знаю), но просто выкинуть его — он права не имеет: код Foo::~Foo() обязан быть вызван до std::terminate, а использование std::endlобязано вызвать flush(), так что yay dtor вы увидите точно. Вот что вы увидите потом — это уже зависит от компилятора, а также от того, что программа установила (если установила) в std::terminate_handlerе…
UFO landed and left these words here
Implementation-defined и undefined — это две большие разницы. На тех платформах, где я работаю стек раскручивается и деструкторы вызываются. В том числе в вашем примере. Интересно какие платформы имели в виду разработчики стандарта, где этого не происходит.
UFO landed and left these words here
UFO landed and left these words here

Вы невнимательно читаете между слов:


и бросать исключение надо в ожидании, что если его никто не ловит, то есть пусть падает

terminate плох тем, что его нельзя нормально поймать, никак. Если он произошел — то уже все. Смысл исключений — в том, что их можно поймать, но если исключение не поймали — то лучше упасть чем продолжить работу.

Там нет "если". Да и странное предложение у вас получается. Что тут ожидать? Ясно что упадёт, если не поймают. Вот исходная фраза понятна, но я ней не согласен. Бросать надо только в надежде, что кто то поймает и обработает. Если ситуацию обработать невозможно, нет смысла и бросать исключение.

Ясно что упадёт, если не поймают.

В случае кодов возврата это не так. И это зачастую сильно запутывает отладку.

UFO landed and left these words here

Кто сказал, что речь о библиотеке?

UFO landed and left these words here
> Мода тут ни при чем.

В модных языках типа go/rust свой появился свой взгляд на то как обрабатывать ошибки, потому и спрашиваю. В статье например исключение бросается когда файл не найден внутри fopenX, и мне такое подход для общего случая не очень нравится, ведь отсутствия файла это обычно ожидаемая ситуация а не исключительная.
UFO landed and left these words here

Только строго наоборот. Исключение (exception) — это особая (исключительная) ситуация, которая требует особой же обработки. А ошибка (fault) — это ситуация, когда программа уже работает неправильно, и исправить тут ничего нельзя.

UFO landed and left these words here

В прикладном коде удобно использовать исключения. Идеологически можно обосновать так:


  • при написании системного сервиса или веб-сервера отказ — это типичная ситуация, и коды возврата будут хороши, а ещё лучше, на мой взгляд, std::expected
  • при написании прикладной бизнес-логики отказ означает провал всей операции (либо целого варианта её выполнения), и исключение — это лучший способ раскрутить стек до того момента, где мы можем сообщить пользователю об ошибке либо обнаружить известную проблему и пойти по запасному плану

Опять же, если есть удобный шаблон вида std::expected, то можно написать вариант fopenX без выброса исключений. Будет примерно как в Rust/Go.

Кст в первых двух блоках кода показана очень распространенная проблема форматирования кода — избыточные уровни вложения. Всегда пытаюсь бороться с лишними уровнями вложения. Каждое условие выбрасывает исключение, то есть прерывает выполнение. Это делает ненужным последующие else.
Джуниоры не всегда знают, как создавать свои RAII для управления ресурсами. Но мы-то знаем:

Нет, вы тоже не знаете. Правильный вариант:


 auto f = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen(path, mode), &std::fclose);

Если вы используете ifstream, то с обработкой ошибок попытка открыть файл выглядит так:

Разве что, "джуниоры". Остальные разработчики проверяют булево состояние потока вначале (необязательно) и при каждой операции чтения:


std::ifstream fs ( "foo.txt" );
if (!fs) std::cerr << std::strerror(errno); // можно, если очень хочется, но не обязательно
std::string s;
while (getline(fs, s)) { ... }

Я не могу назвать такую специализацию unique_ptr правильным вариантом, потому что всегда придётся в конструктор передавать второй аргумент, т.к. подходящего дефолтного конструктора у указателя на функцию нет. Поэтому и заводят структуру с одним оператором.

Нет, вы тоже не знаете. Правильный вариант:
Всё-таки автор более корректен в объявлении deleter'а: во-первых, как было отмечено, второй аргумент теперь будет являться обязательным в конструкторе, а во-вторых, размер такого unique_ptr'а раздувается на размер хранимого указателя на функцию-удалитель (несмотря на то, что fclose по сути является глобальной свободной функцией). Сравнение размеров.
Если такой unique_ptr является членом RAII-класса, то размер может иметь значение. Однако если обработка выполняется только в фиксированном {}-скопе (т.е. C-указатель не покидает блока), действительно можно обойтись указанным объявлением, хотя выигрыш в количестве кода при хорошем форматировании сомнителен.
Код первого примера
....
HANDLE fileLock = CreateFileW(
....
 if (!fileLock)
 {
   CloseHandle(fileLock);
....

Баг №1:
CreateFileW не умеет возвращать нулевое значение совсем (см MSDN). В слечае ошибки возвращается INVALID_HANDLE_VALUE которое суть (HANDLE)(-1). Условие не сработает никогда.

Баг №2:
Если вдруг Windows совсем заболеет и CreateFileW всё-же вернёт 0 то в условии вы закрываете нулевой хэндл.
На этот случай CloseHandle может кинуть exception invalid handle.

Читать дальше перехотелось.
Вот почему в ядре linux пишут на C, а не на C++.
Если уж с открытием фала такой геморой, представлю что там со всем остальным.
Если вы не доверяете программисту пишите на Go.
есть три недостатка: она принимает параметры по указателям

ОК, но ваша fopen5 тоже принимает параметр по указателю — const char* mode. или есть на то причина? как с ним быть?

с модулями компиляция (без компоновки) станет намного быстрее

а что именно станет быстрее?

Я думаю мне стоило заменить const char* mode на const char mode[]. Для анализатора это может дать верную подсказку.
P.S. с модулями все заголовки STL будут разбираться только один раз, а сейчас без хороших precompiled headers они разбираются парсером каждый раз при компиляции очередного файла. Частично precompiled headers решают эту задачу, но хранение на диске в виде интерфейса модуля будет компактнее, чем хранение в виде сериализованного AST. Т.е. модули грозят быть быстрее precompiled headers в плане нагрузки на I/O между запусками компилятора системой сборки, кроме того, суженному интерфейсу легче поместиться в уровни кеша процессора.

UFO landed and left these words here

Если вместо //..остальной код вставить реальный код, бросающий исключение, то уже открытый ранее файл никем не будет закрыт. Я даже встречал связанный с этим баг в одной из библиотек: при ошибке чтения PNG из файла файл становился заблокированным на чтение самим же процессом, потому что где-то в библиотеке утёк FILE*.

UFO landed and left these words here
> не пишите низкоуровневые циклы for и while
> используйте алгоритмы и другие средства из STL/Boost
Зачем вы так с новичками? А если они поверят что STL/Boost это хорошо?
Вырезка из википедии(цитата Торвальдса)
С++ приводит к очень, очень плохим проектным решениям. Неизбежно начинают применяться «замечательные» библиотечные возможности вроде STL, и Boost, и прочего мусора, которые могут «помочь» программированию, но порождают:
— невыносимую боль, когда они не работают (и всякий, кто утверждает, что STL и особенно Boost стабильны и портируемы, настолько погряз во лжи, что это даже не смешно)
— неэффективно абстрагированные программные модели, когда спустя два года обнаруживается, что какая-то абстракция была недостаточно эффективна, но теперь весь код зависит ото всех окружающих её замечательных объектных моделей, и её нельзя исправить, не переписав всё приложение.
Страсть, как ненавижу иностранный словечки. Джуниор: Junior — имеет 5 разных значений в зависимости от страны(Британия или США).

Можно просто новичок или начинающий?
>auto f = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen(path, mode), &std::fclose);

Выглядит четко. Только вот состояние unique_ptr придётся еще дополнительно проверить, легче обернуть fopen в функцию и выкинуть exception на конструкции указателя, чем проверять потом его состояние.

>Разве что, «джуниоры». Остальные разработчики проверяют булево состояние потока вначале (необязательно) и при каждой операции чтения:
>необязательно
А как же выбросить exception на свалившееся открытие? У автора написано грамотнее.
Sign up to leave a comment.

Articles