Здравствуйте, kov_serg, Вы писали:
NB>>кто-то забыл проинитить h? _>Видите как просто найти ошибку. Даже не нужно компилировать, достаточно беглого взгляда На C++ иногда приходится очень глубоко копать что бы понять почему поведение отличается от запланированного.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>... EP>Error codes точно также летят наверх неизвестно куда. EP>Хотя часто вообще не летят, ибо на них забивают — пример
Go — яркий пример того, как на уровне языка поддержать "красивую" обработку исключительных ситуаций средствами проверки error code.
В указанном примере непонятно, что мешало сделать удобный макрос обработки кодов ошибки. Лет 10 назад я довольно писал COM-компоненты для Windows, так там вся обработка ошибок шла через работу с error code (a.k.a. HRESULT). И почти во всех случаях (во всяком случае в коммерческой разработке в которой я принимал участие) использовались макросы по обработке этих ошибок. Соглашение по кодированию было довольно простым насколько я помню — все функции работающие с HRESULT возвращают так же HRESULT. Вводится макрос типа CHECK_ERROR который принимает HRESULT и возвращает ошибку если функция закончилась неуспешно (или преобразовывает в другую в зависимости от логики приложения).
Хотя да, без нормально поставленного код-ревью/автоматической валидации кода легко пропустить подобное от недисциплинированного коллеги.
Здравствуйте, smeeld, Вы писали:
S>Здравствуйте, Lonely Dog, Вы писали:
>> Как организовывать обработку ошибок (исключений нет, значит остаются коды возврата или setjmp/longjmp).
S>Обработка исключений в С++ слишком дорогая операция, и для проверки результатов работы каждой функции это явно не годится.
зря столько минусов наставили — это справедливое утверждение для некоторых компиляторов, хотя "слишком" — это все же слишком сильное утверждение
Как минимум размер бинаря снижается на ~20-30% при компиляции С++ программы без поддержкой исключений (gcc -fno-exceptions) из-за удаления ненужных unwind tables из бинарика. Для windows+msvc это скорее всего несправедливо.
Здравствуйте, landerhigh, Вы писали:
L>Здравствуйте, kov_serg, Вы писали:
L>>>Макароны и есть. Хрестоматийные. Всего два ресурса и уже кошмар. _>>Это еще не кошмар. Вы еще кошмаров не видели. L>Сильное заявление.
_>>Привидите пример как вы организуете модули в C++ и я посмотрю у кого макароны. L>Не вижу проблемы, которая стоит заострения внимания.
Очень жаль.
L>>>Добавляем еще пару ресурсов и получаем гнездо багов. _>>Ничего подобного. Всё очень локализовано, изолировано, не связно и легко отлаживается.
L>Отлаживается? Код, который нужно отлаживать — говнокод по определению. Обсуждению не подлежит.
Сильное заявление. "Отлатчики нинужны" То-то я смотрю кругом идеальные программы.
Собственно напомню, что происходит в каждом случае. В функции с исключениями устанавливается фрейм один раз на всю функцию, а в функции с проверкой возвратов каждый раз проверяется возврат. Поэтому если брать не такой вырожденный случай, то функция, обрабатывающая исключения будет выполнятся быстрее в случае отсутствия ошибочных ситуаций.
Здравствуйте, _NN_, Вы писали:
_NN>Получаем разницу 15ms/10*1000*1000 = 1.5ns. _NN>Не так много учитывая упрощение кода.
Здравствуйте, A13x, Вы писали:
A>Go — яркий пример того, как на уровне языка поддержать "красивую" обработку исключительных ситуаций средствами проверки error code.
И что же там красивого? Есть автоматическое протаскивание кодов ошибок или опять лапша?
A>В указанном примере непонятно, что мешало сделать удобный макрос обработки кодов ошибки. Лет 10 назад я довольно писал COM-компоненты для Windows, так там вся обработка ошибок шла через работу с error code (a.k.a. HRESULT). И почти во всех случаях (во всяком случае в коммерческой разработке в которой я принимал участие) использовались макросы по обработке этих ошибок. Соглашение по кодированию было довольно простым насколько я помню — все функции работающие с HRESULT возвращают так же HRESULT. Вводится макрос типа CHECK_ERROR который принимает HRESULT и возвращает ошибку если функция закончилась неуспешно (или преобразовывает в другую в зависимости от логики приложения).
Да, да, работал я как-то с COM, правда не с тем который MS, а с библиотечным (но тоже от огромной корпорации) — там как раз были эти самые "удобные макросы" — и что характерно в приличной части случаев на эти макросы с этими назойливыми кодами ошибок благополучно забивали
Здравствуйте, kov_serg, Вы писали:
_>>>Привидите пример как вы организуете модули в C++ и я посмотрю у кого макароны. L>>Не вижу проблемы, которая стоит заострения внимания. _>Очень жаль.
Да-да, мне нравится этот троллинг. С одной стороны приводим пример макарон, которые ничего полезного не делают, кроме заката солнца вручную, а с другой стороны требуем от собеседника свержения гор. Так толсто, что даже тонко.
L>>Отлаживается? Код, который нужно отлаживать — говнокод по определению. Обсуждению не подлежит. _>Сильное заявление. "Отлатчики нинужны" То-то я смотрю кругом идеальные программы.
Совершенно верно. Пока разработчики будут продолжать "отлаживать" свеженаписанный код, ситуация не изменится.
Здравствуйте, A13x, Вы писали:
>>> Как организовывать обработку ошибок (исключений нет, значит остаются коды возврата или setjmp/longjmp). S>>Обработка исключений в С++ слишком дорогая операция, и для проверки результатов работы каждой функции это явно не годится. A>зря столько минусов наставили
Зря ты это сказал.
A>- это справедливое утверждение для некоторых компиляторов, хотя "слишком" — это все же слишком сильное утверждение A>Как минимум размер бинаря снижается на ~20-30% при компиляции С++ программы без поддержкой исключений (gcc -fno-exceptions) из-за удаления ненужных unwind tables из бинарика.
Так нужно же сравнивать не просто с выключенными исключением, а с аналогичной лапшой if error goto.
Я кстати уже делал тест
Здравствуйте, A13x, Вы писали:
A>В указанном примере непонятно, что мешало сделать удобный макрос обработки кодов ошибки. Лет 10 назад я довольно писал COM-компоненты для Windows, так там вся обработка ошибок шла через работу с error code (a.k.a. HRESULT). И почти во всех случаях (во всяком случае в коммерческой разработке в которой я принимал участие) использовались макросы по обработке этих ошибок.
И в итоге линейный код пестрел пачками CHECK_HR(...), превращающими его в первосортное УГ. Особенно эпично это выглядело при использовании какого-нибудь OLE DB.
A>Соглашение по кодированию было довольно простым насколько я помню — все функции работающие с HRESULT возвращают так же HRESULT. Вводится макрос типа CHECK_ERROR который принимает HRESULT и возвращает ошибку если функция закончилась неуспешно (или преобразовывает в другую в зависимости от логики приложения).
И результатом этого простого соглашения потом были долгие поиски причины какого-нибудь E_FAIL в недрах DCOMа.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
A>>Go — яркий пример того, как на уровне языка поддержать "красивую" обработку исключительных ситуаций средствами проверки error code.
EP>И что же там красивого? Есть автоматическое протаскивание кодов ошибок или опять лапша?
Там можно одновременно вернуть из функции несколько значений:
И если функция возвращает несколько значений, то молча игнорировать "лишние" синтаксически недопустимо (но можно явно сказать, что какие-то значения мне не нужны).
Ошибка же там — это не код, а интерфейс, с единственным обязательным методом, преобразующим ошибку в осмысленную для человека строку.
Никаких автоматических протаскиваний кодов ошибок там нет.
Здравствуйте, A13x, Вы писали:
A>Go — яркий пример того, как на уровне языка поддержать "красивую" обработку исключительных ситуаций средствами проверки error code.
Что там красивого? На эту проверку вполне можно забить.
Если уж говорить про нормальную проверку через коды возврата, то лучше на примере Rust-а, куда добавили алгебраические типы данных и паттерн-матчинг.
Здравствуйте, antropolog, Вы писали:
A>код возврата OpenFile. Я зову асинхронно функцию OpenFileAsync, передаю ей коллбек для возврата результата. Коллбек on_result(FILE). У меня не возникло проблем. Проблемы у тебя, где поставить try catch.
Я тебя понял — ты (КЕП!) пытаешься показать, что асинхронный механизм с синхронным плохо совмещается. Но почему-то приплетаешь сюда исключения. Но это не проблема исключений. Это всё равно, что я буду говорить, что коды возврата отстой, потому что я хочу получить получить код возврата сразу при передаче коллбека, а он мне не даёт! Это не проблема кодов возврата как и не проблема исключений.
Ещё раз — исключение передаёт информацию о проблеме выше по стеку вызовов — где стек вызовов в асинхронной модели? Вот то то же и оно.
Во вторых, если как ты говоришь OpenFileAsync — функция асинхронная, она просто не может использовать синхронный механизм, то есть исключения она бросать не может. Она должна вернуть тебе std::future, который прозрачно бросит исключение при доступе к данным.
В третьих, если у тебя полностью всё асинхронное, то никаких on_result(FILE) быть не должно, так как обработка ошибок должна вестись в асинхронном режиме тоже. Должно быть on_success и on_fail, в коллбек параметра которого пихается код/объект проблемы. Но это не код возврата, тут возврата нет, потому и проблем с ними нет. Тут проблемы другого рода.
В полностью асинхронной программе архитектура должна быть быть такой, чтобы все проблемы можно было обработать на месте возникновения, что сильно ограничивает область её применения, либо архитектуру (к примеру, полностью событийная архитектура, где ни коды возврата, ни исключения не используются).
A>я хочу понять как сделать хорошо с исключениями
Выбираешь точки обработки ошибок и пишешь в оптимистическом ключе. Гораздо проще чем использовать коды возврата.
P>>std::promise/std::future A>зачем мне эти инструменты? Это монады для вытаскивания отложенных значений. В моём примере коллбек зовётся только тогда, когда результат (или его отсутствие) уже готов. Зачем здесь promise/future?
Это то, с помощью чего достигается совмещение асинхронного и синхронного кода на плюсах.
A>Смотри: A>
A>optional<DATA> loadData(filename) {
A> optional<DATA> data;
A> if ( optional<FILE> f = OpenFile(filename) ) {
A> // OK. File is found.
A> data = ReadFromFile(*f);
A> }
A> else if ( optional<NETFILE> f = OpenFileFromNetwork( config::get_remote_url() + filename) )
A> {
A> // Otherwise, try to open file from network share
A> data = ReadFromNetFile(*f);
A> }
A> else if ( optional<FILE> f = OpenFile("default.file") )
A> {
A> // Otherwise, try to read data from the default file
A> data = ReadFromFile(*f);
A> }
A> else if ( optional<NETFILE> f = OpenFileFromNetwork(config::get_remote_url() + "default.file") )
A> {
A> // Otherwise, try to read data from the default file from network
A> data = ReadFromNetFile(*f);
A> }
A> return data;
A>}
A>
A>Очевидно, что для клиентского кода, ошибка в случае OpenFile и ReadFromNetFile вполне ожидаема, и для неё отдельные бранчи. Как loadData будет выглядеть в случае с исключениями?
Ну, типичный индусский код — наличие файла проверяется косвенным способом, loadData выполняет не столько load сколько поиск файла, данная сигнатура не позволяет клиенту узнать почему нет данных — то ли файл не нашли, то ли он был пустой и могли искать дальше. Как этот код прошёл у вас ревью непонятно.
А где собственно обработка кодов возврата? Её тут нет.
Или ты считаешь, что bool — это и есть код возврата? Тогда понятно, почему не видишь проблем, возникающих с ними.
Возвращение bool подразумевает ветвление, что означает возможность локальной обработки. Фактически bool — это не исключительная, а штатная ситуация.
Вот пример, что такое коды исключений: https://msdn.microsoft.com/ru-ru/library/windows/desktop/aa365430(v=vs.85).aspx , а вот возможные значения https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms681381(v=vs.85).aspx .
И их надо обрабатывать для каждой функции. Потом, в случае проблемы передать наверх по стеку для для обработки. Учти, что клиентская функция как правило использует не одну, а несколько функций + имеет и свои проблемы, о которых должна сигнализировать с помощью кодов возврата. И всё это добро надо доставить в нужное место, так как чрезвычайно редко в реальных программах проблему возможно адекватно обработать на месте её возникновения.
A>Вот это новости. Правильно ли я понимаю, что я должен всегда чекать всё, что потребуется OpenFile, чтобы она ненароком не выкинуло исключений? (ну там, наличие файла, права доступа, количество доступных файловых хендлов в системе, итд ).
Я даже не знаю что тебе на это ответить. Нет. Ты должен обеспечить наличие всего требуемого ещё до вызова loadData, а что не смог обеспечить — то проверить в процессе.
(Иначе получается ситуация: сел поесть, но оказалось что на котлете нет подливы, как собственно и котлеты, да чего уж — и тарелка отсутствует, и сидишь не на кухне, и даже не у себя дома)
A>Но даже с синхронными вызовами, какого типа исключения должен ловить фремворк, например, который зовёт твои функции? (Подсказка: Откуда он будет знать про твои типы исключений? И про boost::exception? )
Фреймворк предоставляет свой набор исключений, который ты можешь использовать либо как есть, либо наследуясь от них.
Здравствуйте, so5team, Вы писали:
A>>Go — яркий пример того, как на уровне языка поддержать "красивую" обработку исключительных ситуаций средствами проверки error code.
S>Что там красивого? На эту проверку вполне можно забить.
Можно, естественно. Go — практический язык, и его создатели прекрасно понимали, что бывают случаи, когда проверка на ошибку не нужна. Но это придется написать явно, просто "забыть" не получится.
S>Если уж говорить про нормальную проверку через коды возврата, то лучше на примере Rust-а, куда добавили алгебраические типы данных и паттерн-матчинг.
В go ошибка — это не код возврата, а интерфейс. Можно (и вполне идеоматично) возврашать разные типы на разные ошибки и паттерн-матчить по типу.
Здравствуйте, Lonely Dog, Вы писали:
LD>Я 15 лет пишу на C++, немного знаю C#, Python и пр. Но совершенно не понимаю, как писать на C. LD>Может есть какие-нибудь книги? Думаю, посмотреть что-то типа Writing device drivers for Linux.
Если это не вброс — зачем сегодня писать на чистом Си ? ИМХО под любую вменяемую архитектуру сущесвтуют С++ компиляторы.
Писал прошивку под STM32F103 около 4 лет назад, для автомобильного CanBus адаптера в свою машину. На Keil. Вполне себе на С++.
Сейчас, правда, приходится линукс драйверы на чистом СИ писать, с матерком. Из-за совершенно ублюдского kbuild, чтоб они все там передохли. Как нибудь соберусь с духом — и запилю свою систему сборки, с блекджеком и шлюхами человеческим лицом. Только на секунду представь "качество" красноглазого решения: для драйвера, лежащего вне основной ветки, невозможно разделить каталоги исходника и временных файлов — это поделие всенепременнейше срёт в одну кучу. B ещё пачку временных файлов и каталогов в корень проекта заодно. (да, я знаю про опции M и O). Передача CFLAGS и т.д. такая же кривая.
Здравствуйте, antropolog, Вы писали:
A>это несомненно. Вопрос больше в том, почему некоторые считают механизм исключений основой обработки ошибок.
Потому что, он очень практичен. Ты не понимаешь этого потому, что не пользуешься даже кодами возврата (bool — это не код возврата).
A>В том то и дело, что это может решить только клиентский код (тот что выше по стеку). А при написании сервисного кода (того что ниже по стеку) такие решения всегда очень спекулятивны.
Ты описываешь типичный антипаттерн программирования. Это всё уже было в истории. А потом изобрели ООП (в частности инкапсуляцию, которая как раз и скрывает от клиента то, что ему не нужно знать) и жизнь стала налаживаться.
Чисто даже логически то, что это клиент должен решать есть проблема у компонента или нет — это бред, так как только сам компонент владеет наиболее полной информацией о своём выполнении и своих проблемах. А значит ему и решать, когда у него проблемы, а когда нет. Клиентский код должен только реагировать на сигналы о проблемах.
Здравствуйте, Pzz, Вы писали:
S>>Что там красивого? На эту проверку вполне можно забить.
Pzz>Можно, естественно. Go — практический язык, и его создатели прекрасно понимали, что бывают случаи, когда проверка на ошибку не нужна. Но это придется написать явно, просто "забыть" не получится.
И никто по рукам не стукнет.
S>>Если уж говорить про нормальную проверку через коды возврата, то лучше на примере Rust-а, куда добавили алгебраические типы данных и паттерн-матчинг.
Pzz>В go ошибка — это не код возврата, а интерфейс. Можно (и вполне идеоматично) возврашать разные типы на разные ошибки и паттерн-матчить по типу.
Во-первых, никто не стукнет по рукам, если паттерн-матчинг(?) будет не полным. Да и не паттерн-матчинг там будет, а попытка приведения к конкретному интерфейсу.
Во-вторых, подход Go гораздо хуже подхода языков с АлгТД, где нужно явно прописывать варианты для разных исходов. Сравните показанный выше пример ошибки, которую легко совершить в Go, с тем, как это было бы в Rust-е:
Это и есть самые типичные макароны. При увеличении количества ресурсов будет всё веселее и веселее.
Чисто на вскидку, даже учитывая что для уменьшения размеров ты писал по нескольку операторов на строке чего в реальном проекте тебе никто не даст сделать, посмотри сколько у тебя значимых строк (которые выполняют полезную работу), и сколько вспомогательных/технических (по сути мусор) — которые нужны только для обеспечения корректной работы значимых строк. В случае RAII и исключений количество технических строк будет равно нулю.
Здравствуйте, push, Вы писали:
P>Ещё раз — исключение передаёт информацию о проблеме выше по стеку вызовов — где стек вызовов в асинхронной модели? Вот то то же и оно.
Кстати, асинхронные модели сейчас частенько применяются из-за недостатков OS, а не из революционной необходимости. Эта асинхронная лапша во многих случаях выпрямляется во вполне себе синхронный код с помощью сопроцедур.
В частности пример из Boost.Asio: