Comments 63
А вся статья, как бы, описывает: как стоило бы решить ту же проблему нормально…
не все, большинство делают запрос к ФС.
да и опять же, тут как с проверкой интернета, надо сразу делать то, что надо и корректно обрабатывать ошибки.
Под windows — используют FindFirstFileW() и stat() под unix
иначе вы расширяете проверку до «есть ли файл и можно ли его открыть текущему польвавателю»
Исправил в статье.
Теперь в ветке #else
переменная err не инициализирована и при корректном file может содержать мусор, что превратится в фальшивую ошибку. Я не люблю функции с суффиксом "_s" как раз по этой причине: они создают видимость более безопасного решения, при этом зачастую даже провоцируют ошибки, как scanf_s. Ну и кроме того, это специфичный для компилятора код, введённый без особых причин. На использование wchar_t версии хотя бы есть причина — поддержка Unicode-путей.
На самом деле, надо проверять не только _MSC_VER, но и __STDC_VERSION__ ...
Если "пусть падает", то юзайте сразу terminate. Зачем в таком случае исключение бросать?
Да, буду говорить если падать, то давайте оставим открытые сокеты, висячие коннекты, залоченные файлы, кучу мусора в папке temp. Что в этом плохого? Система всё почистит. Это в любом случае возможный исход работы программы. Представьте что будет если внезапно отключить питание.
Раскрутка стека нужна, только если программа будет продолжать работать. Если падаем, то раскручивать смысла нет.
Что такое "второй круг"?
Вы сказали исключение никто не ловит, а теперь у вас уже какие то логи.
Вот в том и дело, что обычный рабочий момент приравнивается к внезапному отключению питания. Давайте будем взрывать системный блок, если не нашли своего раздела в реестре?
то, что некий модуль считает фатальной ошибкой, на высшем уровне иерархии может таковой не являться (например, есть другой модуль, решающий ту же задачу иначе)
Мы говорим про "обычный рабочий момент" или про "дальше жить незачем"?
От того, что некая клетка организма умерла, вовсе не следует, что и весь организм должен за ней последовать. Но клетка для себя — умерла.
Неуместное сравнение.
Не понимаю, чего вы от меня добиться хотите.
Хочу, что б вы признали, что исключения, которые никто не ловит, бессмысленны.
Но вы предлагаете еще и всю программу свалить, причем самым дурацким из возможных способом, не дав ей за собой почистить.
Может вы перестанете сыпать эпитетами и скажете, наконец, что конкретно не так с отсутсвием освобождения ресурсов при завершении программы?
Исключения, которые никто не ловит, это единственный способ аварийно завершить программу, написанную на C++ (а не на C). Но я еще раз повторю, речь шла не об этом.
Ага, как же. terminate, exit, return -1 из main в конце концов. А о чём тогда?
Конкретно. Как будет закрыто TCP-соединение, если оно существовало в момент заверешения? Так оно должно закрываться? Как будет завершена сессия TLS поверх этого соединения? Так она должна завершаться? Что станет со встроенной базой данных на sqlite? Что сделается с криптоключами, созданными программой? Чем закончится асинхронная запись в файл? Даже попросту что произойдет с иконкой программы в трее?
Почаще задавайте себе такие вопросы. TCP-соединение отвалится по таймауту, соответственно, и tls сессия. С sqlite все будет отлично. Там транзакционноссть гарантирует целостность данных. Про ключи не понял. С иконкой, действительно, беда, но это в первую очередь демонстрирует кривость windows.
Стоит уточнить, что я не утверждаю, что исключения не нужны, что terminate можно использовать где угодно, и, конечно, что ресурсы не нужно освобождать перед завершением работы программы. Я хочу скзать, что в случаях, когда ошибку обработать невозможно, к примеру при инициализации глобальных объектов, нужно вызывать terminate, а не бросать какие-то исключения.
Я не понял, что вы хотели сказать фразой
бросать исключение надо в ожидании, что его никто не ловит, то есть пусть падает, все равно уже «дальше жить незачем».
По-вашему, если в этом конструкторе произдёт ошибка, то исключение бросать не нужно что ли? Ерунда какая-то.
Убегающее за пределы main исключение — это UBС какого перепугу? Вы про std::terminate вообще что-нибудь слышали?
Есть все шансы, что yay dtor
вы не увидите.
Это почему ещё? Да, компилятор может «просечь», что в этой программе обязательно будет вызван деструктор, а за ним — std::terminate
и вместо компиляции сложных таблиц и всего прочего просто напрямую вызвать Foo::~Foo()
(хотя я таких компиляторов не знаю), но просто выкинуть его — он права не имеет: код Foo::~Foo()
обязан быть вызван до std::terminate
, а использование std::endl — обязано вызвать flush(), так что yay dtor
вы увидите точно. Вот что вы увидите потом — это уже зависит от компилятора, а также от того, что программа установила (если установила) в std::terminate_handlerе…Вы невнимательно читаете между слов:
и бросать исключение надо в ожидании, что если его никто не ловит, то есть пусть падает
terminate плох тем, что его нельзя нормально поймать, никак. Если он произошел — то уже все. Смысл исключений — в том, что их можно поймать, но если исключение не поймали — то лучше упасть чем продолжить работу.
В модных языках типа go/rust свой появился свой взгляд на то как обрабатывать ошибки, потому и спрашиваю. В статье например исключение бросается когда файл не найден внутри fopenX, и мне такое подход для общего случая не очень нравится, ведь отсутствия файла это обычно ожидаемая ситуация а не исключительная.
В прикладном коде удобно использовать исключения. Идеологически можно обосновать так:
- при написании системного сервиса или веб-сервера отказ — это типичная ситуация, и коды возврата будут хороши, а ещё лучше, на мой взгляд, std::expected
- при написании прикладной бизнес-логики отказ означает провал всей операции (либо целого варианта её выполнения), и исключение — это лучший способ раскрутить стек до того момента, где мы можем сообщить пользователю об ошибке либо обнаружить известную проблему и пойти по запасному плану
Опять же, если есть удобный шаблон вида std::expected, то можно написать вариант fopenX без выброса исключений. Будет примерно как в Rust/Go.
Джуниоры не всегда знают, как создавать свои 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.
Читать дальше перехотелось.
Если уж с открытием фала такой геморой, представлю что там со всем остальным.
Если вы не доверяете программисту пишите на Go.
есть три недостатка: она принимает параметры по указателям
ОК, но ваша fopen5 тоже принимает параметр по указателю — const char* mode. или есть на то причина? как с ним быть?
с модулями компиляция (без компоновки) станет намного быстрее
а что именно станет быстрее?
Я думаю мне стоило заменить const char* mode
на const char mode[]
. Для анализатора это может дать верную подсказку.
P.S. с модулями все заголовки STL будут разбираться только один раз, а сейчас без хороших precompiled headers они разбираются парсером каждый раз при компиляции очередного файла. Частично precompiled headers решают эту задачу, но хранение на диске в виде интерфейса модуля будет компактнее, чем хранение в виде сериализованного AST. Т.е. модули грозят быть быстрее precompiled headers в плане нагрузки на I/O между запусками компилятора системой сборки, кроме того, суженному интерфейсу легче поместиться в уровни кеша процессора.
Если вместо //..остальной код
вставить реальный код, бросающий исключение, то уже открытый ранее файл никем не будет закрыт. Я даже встречал связанный с этим баг в одной из библиотек: при ошибке чтения PNG из файла файл становился заблокированным на чтение самим же процессом, потому что где-то в библиотеке утёк FILE*
.
> используйте алгоритмы и другие средства из STL/Boost
Зачем вы так с новичками? А если они поверят что STL/Boost это хорошо?
Вырезка из википедии(цитата Торвальдса)
С++ приводит к очень, очень плохим проектным решениям. Неизбежно начинают применяться «замечательные» библиотечные возможности вроде STL, и Boost, и прочего мусора, которые могут «помочь» программированию, но порождают:
— невыносимую боль, когда они не работают (и всякий, кто утверждает, что STL и особенно Boost стабильны и портируемы, настолько погряз во лжи, что это даже не смешно)
— неэффективно абстрагированные программные модели, когда спустя два года обнаруживается, что какая-то абстракция была недостаточно эффективна, но теперь весь код зависит ото всех окружающих её замечательных объектных моделей, и её нельзя исправить, не переписав всё приложение.
Можно просто новичок или начинающий?
Выглядит четко. Только вот состояние unique_ptr придётся еще дополнительно проверить, легче обернуть fopen в функцию и выкинуть exception на конструкции указателя, чем проверять потом его состояние.
>Разве что, «джуниоры». Остальные разработчики проверяют булево состояние потока вначале (необязательно) и при каждой операции чтения:
>необязательно
А как же выбросить exception на свалившееся открытие? У автора написано грамотнее.
Повседневный C++: изолируем API в стиле C