Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.133.120.64] |
|
Сообщ.
#1
,
|
|
|
Всем приветище в этом чятике!
Так получилось, что большую, скажем так, профессиональную часть своей работы с С++ я провел исключительно в симбиозе с Qt. А там, увы, свои правила. В документации (щя не скажу, или сами найдёте, ну или я потом) написано английским по белому "не используйте обработку исключений, наша либа это категорически не приветствует". Естественно не дословно. И эта часть языка С++ (обработка исключений) до сих пор для меня является чисто академической. Да, я знаю синтаксис, напишу любые примеры применения ... Но это для меня до сих пор является предметом не моей практики. Поэтому просьба, поделиться "лайф-хаками", когда использование исключений и последующего "разворачивания стека" - делает код близким к идеалу. Просто несистемные примеры, потом разберёмся. Кстати. Если в том же С++ есть участки кода без задействования "механизмов" Qt - у меня есть полное и честное право использовать обработку исключений. Кстати да! Есть две, скажем так, парадигмы, которые меня очень интересуют в разработке - "Шаблоны проектирования от банды четырёх" и принципы "S.O.L.I.D". И если у вас получатся примеры лайфхаков в разрезе этих доктрин - мое к вам уважение на этот месяц превысит все допустимые пределы!!! |
Сообщ.
#2
,
|
|
|
Методика обработки ошибок слишком комплексна, чтобы можно было определить чёткие критерии выбора той или иной. Самый простой критерий якак-то прочитал... вот не помню, наверное Саттера, но это неточно, и он гласит, что исключения обязательно должны использоваться, когда нет других способов сообщить об ошибке и предотвратить выполнение потока исполнения, кроме как вернуться к предыдущему, гарантировано безопасному, состоянию. В этом смысле исключения являются развитием парадигмы транзакционной целостности исполнительного окружения. Ну т.е. просто мы помечаем некое место в коде, к которому можно безопасно откатиться в случае, если транзакцию следует откатить, а не подтвердить. В остальных случаях использование исключений следует минимизировать, т.к. они по своей природе неструктурны, что противоречит парадигме структурного программирования. Типичный пример, когда они не нужны – валидация введённых пользователем данных. Типичный пример, когда нужны – подсистема не предусматривает того, что запрошенный контракт может быть не выполнен. Как вариант: интерфейс подсистемы может быть перепроектирован, но это сложно, неудобно или ещё чего. Вот std::vector<>::at() возвращает просто ссылку, и это удобно. В случае если возвращать нечего, нет никаких других вариантов, кроме исключения. Можно перепроектировать, чтобы был, но это будет жутко неудобно. К тому же есть альтернативный вариант в явной проверке перед вызовом std::vector<>::at(), который в этом случае выгоднее заменить на std::vector<>::operator[], поэтому перепроектирование в целом сделает только хуже.
Но минимизировать не значит отказаться, как и возврат к безопасному состоянию может быть осуществлён не только исключениями. Самый важный + от исключений — автоматическое, гарантированное языком, разрушение всех объектов со временем жизни в диапазоне от точки безопасного отката до обнаружения отказа. Это позволяет настраивать методику отката по специфическим конкретно для этого места в коде потребностям. |
Сообщ.
#3
,
|
|
|
Цитата Qraizer @ В этом смысле исключения являются развитием парадигмы транзакционной целостности исполнительного окружения. Да, похоже это самое главное преимущество. Когда, в случае исключительных ситуаций, все цепочки деаллокаций и разрушений временных объектов как-бы прячутся "под капот". |
Сообщ.
#4
,
|
|
|
Ну, не деаллокаций всё ж. Скажем так, функций очистки. Где-то это free() или operator delete, где-то socketclose(), где-то CloseHandle(), а где-то RollbackTransaction(). Та неважно, что в деструктор впихнёшь, то и исполнится. Аллокации сами по себе, если я понял о чём ты, создают объекты с временем жизни, выходящим за рамки структуры кода, поэтому какой-нибудь malloc() или operator new не будут отменены, для этого требуются объекты, владеющие этими аллокациями. Любые контейнеры, например, таковыми являются, так что std::vector<> или даже std::list<> вполне себе очистятся. На худой конец есть std::shared_ptr<> или std::unique_ptr<>. Просто перестаём пользоваться сырыми указателями, и смеёмся над тупыми властями тупых государств, где работают тупые программеры.
Добавлено Я бы сказал, что исключения являются более безопасной к ошибкам парадигмой, т.к. заставляет программистов обрабатывать отказы. Ибо один из самых главных врагов тут – лень. Вот не проверил состояние ошибки, и всё, код пошёл в разнос. Где-то потом упадёт, ищи-свищи, откуда оно взялось, где-то UB, ещё "лучше". Если вообще упадёт, а то и годами кое-как на соплях работать будет. Конечно, нынче можно завести std::optional<> или там template <typename ...Args> using /*...*/ = std::variant<Args..., std::error_code>, но оба варианта всё равно предполагают, что программер будет проверять, что там лежит. Да, если не проверит и заюзает отсутствующий результат, получит исключение, но даже так это будет просто bool, весь контекст будет утерян. Но код хотя бы стопнется до того, как делов наворотит. Явно заюзав исключения, ты и контекст туда впихнуть сможешь, и либо заставишь программистов их перехватить, чтоб обработать, либо оно само вовремя упадёт, как в случае std::optional<>, так что хуже не будет. А пользователи потом таких горемык закидают тапками в саппорте. За это приходится платить. Производительность исключений может пессимизировать код довольно сильно. Тут уж надо смотреть, важнее ли надёжность производительности. Обычно, скорость отклика UI или сетевого взаимодействия это позволяют, но бывают нюансы. |
Сообщ.
#5
,
|
|
|
Цитата Majestio @ Поэтому просьба, поделиться "лайф-хаками", когда использование исключений и последующего "разворачивания стека" - делает код близким к идеалу. Рихтер. "Создание эффективных приложений...". Глава 24. Читаем, по результатам пишем процедуру-фильтр. Которая выводит контекст, код исключения, имя файла и номер строки, где поймано исключение. Можно ещё отрезок двоичных кодов программы, вокруг места с исключением. (Неплохо также версию OC и версию, билд приложения). Всё это выводим в интерфейс вывода. Чтобы легко назначать вывод куда захочется в зависимости от конкретного приложения и других обстоятельств. Скрытый текст Например, у меня некоторое количество юзеров с сетевым приложением, я их даже всех не знаю. Как узнать ? Данные исключения выводятся мне на UDP-сервер. --- Основная цель всего этого - по возможности быстрее найти проблемную точку в исходниках. А там дальше как получится. Иногда невероятно трудно понять, как возникла исключительная ситуация. Добавлено Цитата Majestio @ Есть две, скажем так, парадигмы, которые меня очень интересуют в разработке - "Шаблоны проектирования от банды четырёх" Посмотри тут |
Сообщ.
#6
,
|
|
|
Цитата Qraizer @ Ну, не деаллокаций всё ж. Скажем так, функций очистки. Где-то это free() или operator delete, где-то socketclose(), где-то CloseHandle(), а где-то RollbackTransaction(). Та неважно, что в деструктор впихнёшь, то и исполнится. Аллокации сами по себе, если я понял о чём ты, создают объекты с временем жизни, выходящим за рамки структуры кода, поэтому какой-нибудь malloc() или operator new не будут отменены, для этого требуются объекты, владеющие этими аллокациями. Любые контейнеры, например, таковыми являются, так что std::vector<> или даже std::list<> вполне себе очистятся. На худой конец есть std::shared_ptr<> или std::unique_ptr<>. Просто перестаём пользоваться сырыми указателями, и смеёмся над тупыми властями тупых государств, где работают тупые программеры. Хе-хе я примерно это и имел ввиду. Хотя возможно использовал не общепринятую терминологию. Под разрушением временных объектов - я понимаю вызов их деструкторов с деаллокацией всего временного по иерархии. А под деаллокацией - освобождение любого рода ресурсов, которые были вызваны в конструкторе/головной программе/служебных функциях. Ну, в общем, ладно - думаю мы поняли друг друга. Цитата Qraizer @ Просто перестаём пользоваться сырыми указателями В обычной жысти С++ прогера они не сильно востребованы, если не совсем. Но иногда же хочется написать аллокатор на сырых указателях для оператора new для последующего интереса чего там настроилось? Признайся! Цитата ЫукпШ @ Читаем, по результатам пишем процедуру-фильтр. Которая выводит контекст, код исключения, имя файла и номер строки, где поймано исключение. Кстати, да - тоже отличное применение! Всегда завидовал Жаба-прогерам в этом плане. У них когда прога падает - они "искаропки" получают весь стек вызовов. Обязательно почитаю Главу 24, возможно получится такой же стек замутить самому для С++. Цитата ЫукпШ @ Посмотри тут Хороший ресурс. Именно по нему, в большей части, я делал для себя свою шпаргалку. И практически во всех случаях там оставлял соответствующую ссылку на этот ресурс. За исключением одного шаблона проектирования "Интерпретатор". Тамошние спецы не считают его "кирпичиком" ввиду большей сложности в реализации. |
Сообщ.
#7
,
|
|
|
Цитата Majestio @ std::stacktraceОбязательно почитаю Главу 24, возможно получится такой же стек замутить самому для С++. Получим что-то типа There are some tests for different functionality units: censored 1.1 лев к censored 1.1 лев м censored 2.1 лев к Stack trace -->: censored\certGenUni\test.cpp, line 557: certGen!fillTests+0x9F2 censored\certGenUni\main.cpp, line 38: certGen!main+0xD5 D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl, line 78: certGen!invoke_main+0x33 D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl, line 288: certGen!__scrt_common_main_seh+0x15A D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl, line 331: certGen!__scrt_common_main+0xD D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp, line 17: certGen!mainCRTStartup+0x8 , line 0: KERNEL32!BaseThreadInitThunk+0x19 , line 0: ntdll!RtlGetAppContainerNamedObjectPath+0x11E , line 0: ntdll!RtlGetAppContainerNamedObjectPath+0xEE There are some tests for different functionality units: censored 1.1 лев к censored 1.1 лев м censored 2.1 лев к Stack trace -->: , line 0: certGen+0x301E9 , line 0: certGen+0xC58D2 , line 0: certGen+0xAB775 , line 0: certGen+0xFAAB3 , line 0: certGen+0xFA92A , line 0: certGen+0xFA7BD , line 0: certGen+0xFAB38 , line 0: KERNEL32!BaseThreadInitThunk+0x19 , line 0: ntdll!RtlGetAppContainerNamedObjectPath+0x11E , line 0: ntdll!RtlGetAppContainerNamedObjectPath+0xEE |
Сообщ.
#8
,
|
|
|
А все равно line 0
|
Сообщ.
#9
,
|
|
|
Qraizer, o5 плюсую
Цитата macomics @ А все равно line 0 Ну, думаю, тут всё будет зависеть от твоей/своей политики лицензирования ... Билды а-ля LTS конечно можно избавить от дебаг-шлака. Но всякие там релиз-кандидаты ... а почему бы и нет?!!! Я так понимаю, что политику лицензирования каждый пипл волен сам выбирать для себя, равно как и декларацию "качества" предоставляемого программного продукта. |
Сообщ.
#10
,
|
|
|
Подозреваю, если закинуть виндовые символы, то и там можно получить вменяемые буковки и циферки. Только толку с этого, коли сырцов всё равно нет
|
Сообщ.
#11
,
|
|
|
Цитата Qraizer @ Подозреваю, если закинуть виндовые символы, то и там можно получить вменяемые буковки и циферки. Только толку с этого, коли сырцов всё равно нет Такую тему, я щетаю, нужно выставлять на конкурс сырцов. С призовым фондом минимум 2000Dgm |
Сообщ.
#12
,
|
|
|
Впрочем, при желании легко отсеять не относящиеся к своему коду строчки внизу отчёта:
IDispatch's error: IDispatch::GetIDsOfNames("TablesOfContents") failed w/err 0x80010001 PropCreator(): src is not an IDispatch: -2147467262 (80004002) Stack trace -->: censored\certGenUni\property.h, line 32: certGen!Property::Property+0x9D censored\certGenUni\cases.cpp, line 1037: certGen!`makeReports'::`2'::<lambda_5>::operator()+0x142 censored\certGenUni\cases.cpp, line 1059: certGen!`makeReports'::`2'::<lambda_6>::operator()+0xA15 censored\certGenUni\cases.cpp, line 1102: certGen!makeReports+0x13DA censored\certGenUni\main.cpp, line 106: certGen!main+0x59B |
Сообщ.
#13
,
|
|
|
Qraizer, плис, накидай академический пример. Так, чтобы виден был последовательный вызов/создание и такой же вызов уничтожения, ну и сам трасерт вызовов. Тематика вааще пофик. Главное - сам пример.
|
Сообщ.
#14
,
|
|
|
Та чё там накидывать... Прям из certGen возьму.
class StackTrace: public std::runtime_error { std::stacktrace stack; public: explicit StackTrace(const std::string& msg): std::runtime_error(msg), stack(std::stacktrace::current()) {} const std::stacktrace& getStack() const throw() { return stack; } void report(std::ostream& os) const; }; // Класс общего исключения COM. Хранит HRESULT помимо текста. class COM_Error: public StackTrace { HRESULT code; public: explicit COM_Error(HRESULT err): StackTrace("A COM error is occured"), code(err) {} COM_Error(HRESULT err, const std::string& msg): StackTrace(msg), code(err) {} ~COM_Error()throw() {} HRESULT why() const throw() { return code; } }; // Класс исключения OLE. Основан на общих исключениях COM. class OLE_Error: public COM_Error { public: explicit OLE_Error(HRESULT err): COM_Error(err, "An OLE error is occured") {} OLE_Error(HRESULT err, const std::string& msg): COM_Error(err, msg) {} ~OLE_Error() throw() {} }; /* тут не заморачиваемся параметризацией типами символов, аллокаторами итп, т.к. при размотке стека по исключению всякие там narrow <=> wide конверсии ...ну их на всякий случай; просто юзаем std::ostream и std::string */ void StackTrace::report(std::ostream& os) const { static const std::string StackTraceClass= "StackTrace"; static const std::string COM_ErrorClass = "COM_Error"; static const std::string OLE_ErrorClass = "OLE_Error"; for (const auto& entry : stack) // срежем само создание исключения с верхушки стека if (entry.description().find(StackTraceClass)== std::string::npos && entry.description().find(COM_ErrorClass) == std::string::npos && entry.description().find(OLE_ErrorClass) == std::string::npos) // <= допишите свои фильтры по желанию os << entry.source_file() << ", line " << entry.source_line() << ": " << entry.description() << '\n'; os.flush(); } /* пример использования; всё как обычно, ничего особенного */ struct PropCreator { static IDispatch* create(const Variant& src) { // создавать свойства на Variant-ах, которые не IDispatch, мы не умеем. if (src.vt != VT_DISPATCH) throw OLE_Error(E_NOINTERFACE, "PropCreator(): src is not an IDispatch"); return src.pdispVal; } }; /* ... */ try { /* ... */ } catch(const COM_Error& exc) { std::clog << exc.what() << ": " << exc.why() << " (" << std::hex << std::uppercase << exc.why() << ")\nStack trace -->:" << std::dec << std::endl; exc.report(std::clog); return EXIT_FAILURE; } catch(const StackTrace& exc) { std::clog << exc.what() << "\nStack trace -->:\n"; exc.report(std::clog); return EXIT_FAILURE; } |
Сообщ.
#15
,
|
|
|
Нет. Твой пример некрасивый и не полный. Сорян!
Давай ты решишь (в рамках сабжа!!!) другую задачу: 1) Задается произвольная матрица целых чисел размерностью 3x3 2) Одно из чисел (пусть одно) получит значение "нуль". 3) Нужно получить матрицу-результат = каждое значение исходной матрицы деленное на минимальное значение чисел из матрицы (что явно не есть нуль, но быть может) Нужно словить (это просто) когда очередной член матрицы делится на минимальное значение матрицы (равное нулю) - и самое главное, словить в расчетах первое деление на нуль. И вывести стек вызовов. Вот это, Qraizer, будет зе бест фром зе уэст! Остальные твои: try { /* ... */ } Кагбэ намекают - слишком тонко для обычного пипла. |
Сообщ.
#16
,
|
|
|
Сообщ.
#17
,
|
|
|
Цитата Qraizer @ Чего? Я там редактировал, пока ты там читал! =) Перечитай есчо раз =) |
Сообщ.
#18
,
|
|
|
Я правильно прочитал. Ты предлагаешь мне схавать аппаратное исключение? Это оффтоп темы
|
Сообщ.
#19
,
|
|
|
Цитата Qraizer @ Я правильно прочитал. Ты предлагаешь мне схавать аппаратное исключение? Это оффтоп темы А ты что, в сказку попал?!! 1) Есть сетевые ошибки, масса 2) Есть ошибки устройств I/O, часто 3) Крайний случай - тупо ошибки RAM, нечасто но видели Я тебе предлагаю это схавать! =) |
Сообщ.
#20
,
|
|
|
Majestio, не рекомендую троллить или подменять термины. Можешь, конечно, подумать на предмет
LONG WINAPI translateException(EXCEPTION_POINTERS *excPtr) { const unsigned EH_EXCEPTION_NUMBER = ('msc' | 0xE0000000); // <- undocumented if (excPtr->ExceptionRecord->ExceptionCode == EH_EXCEPTION_NUMBER) return EXCEPTION_CONTINUE_SEARCH; throw StackTrace(excPtr->ExceptionRecord->ExceptionCode); // dangerously } // нуль int b = 0; int g() { return 123 / b; // деление на нуль } int f() { return g(); } int main() { HANDLE hExc = AddVectoredExceptionHandler(1, translateException); if (hExc == NULL) return std::cout << "AddVectoredExceptionHandler() fails: " << GetLastError() << std::endl, 1; Guard excRem([=](){ RemoveVectoredExceptionHandler(hExc); }); try { f(); } catch(const StackTrace& exc) { std::clog << std::hex << exc.why() << "\nStack trace -->:\n" << exc.getStack(); return 2; } return 0; } Цитата Majestio @ ни к теме C++EH 1) Есть сетевые ошибки, масса 2) Есть ошибки устройств I/O, часто 3) Крайний случай - тупо ошибки RAM, нечасто но видели |
Сообщ.
#21
,
|
|
|
Цитата Qraizer @ Majestio, не рекомендую троллить или подменять термины. И в помыслах не было! Просто я немножко на позитиве в следствие принятия соответствующих, скажем так, пищевых добавок Цитата Qraizer @ Ты предлагаешь мне схавать аппаратное исключение? Желаю уточнить. Мы сейчас говорим о юзер-спэйсе. И все что может быть "выкинуто" аппаратурой - мы не ловим напрямую, а ждем реакции исключительно операционной системы. Работу проги в режиме ядра операционной системы, или тем паче, вообще без операционной системы - мы щяс не рассматриваем. Поэтому да - сетевые ошибки, и ошибки I/O могут уверенно прилетать в юзерспейс, уже тёплые, ламповые, готовые. И там (в юзерспейсе) они могут и должны обрабатываться. Что касается аппаратных ошибок RAM - тут согласен. Эту "беду" на прикладном уровне очень сложно, а чаще и невозможно, победить. Цитата Qraizer @ WINAPI А вот тут ты очень обидно сказал!!! Тем самым проигнорировал линуксоидов, бисидишников и большое количество mac-пролетариев! И кстати тут: Цитата Qraizer @ int g() { return 123 / b; // деление на нуль } В рамках программирования "здорового человека" нужно немного поправить сигнатуру функции. А именно проверять b на нуль. И принимать одно из решений, учитывая тему топика: 1) Выкидывать свое исключение, оставив сигнатуру функции как есть 2) Либо поступить как поступают в других ЯП типа Rust/Dart - возвращать не int, а Result. Который, для упрощения, есть структура с полями "валидное_значение?" и "значение". Ну если не структура, пусть будет std::tuple. Не важно, главное принцип - "видишь деление, проверь". Тоже самое касается и остальных мест, где могут быть проблемсы. |
Сообщ.
#22
,
|
|
|
Ты всё-таки определись, что ты хочешь. О ловле контекста, приводящему к аппаратному отказу, с целью его недопущения я уже писал:
std::optional<int> g() { return b == 0 ? decltype(g())() : 123 / b; // деление на нуль } int f() { return g().value(); } std::variant<int, std::error_code> g() { using retType = decltype(g()); return b == 0 ? retType(std::make_error_code(std::errc::invalid_argument)) : retType(123 / b); // деление на нуль } int f() { return std::get<int>(g()); } Если же мы говорим от аппаратных отказах, то Стандартного способа преобразовать исключения низкого уровня в C++EH не существует и вряд ли когда-нибудь будет существовать. В WINAPI испокон веков существовал SEH (даже в Win16, хоть и в очень зачаточном виде). Это часть API, и это позволяет работать с ними единообразным образом на любых уровнях. Даже в ядре. (Даже C++EH в ядре.) Однако WINAPI поддерживает не только C++, поэтому возлагать на ОС прям вот C++EH шоколадно. Однако несложно реализовать трансляцию SEH в C++EH. Выше корявый, но пример. Что до ваших никсов, то это ваши сложности. У вас есть сигналы, вот с ними и ...думайте. Мне влом, не хватало ещё какой-нить в STM32 под realtime tiny os переносить его десяток исключений с парой десятков их источников. |
Сообщ.
#23
,
|
|
|
Цитата Qraizer @ Ты всё-таки определись, что ты хочешь. А я уже определился! Только одно прошу - давай ты без нервов, давай без них самих! =) В процессе обсуждения вопроса мы выявили два "важных" момента: 1) Обеспечение транзакционной целостности объектов - тут понятно и просто 2) Выброс стека вызовов при ловле исключений - тут (лично для меня пока не просто) Я все "почитаю и изучу". Ток одна просьба. Qraizer, не поднимай ни себе и не остальным нервы. Мы тут все более-менее на равных. Кто-то более компетентен, кто менее. Но в этом суть и соль общения. Очень тебя прошу, ты же профи. Держи себя в рамках, даже если к тебе обращаются специалисты гораздо низшего относительно тебя уровня! Добавлено Qraizer, да, кстати, да ... "Деление на нуль" - наверное одно, и наверное, единственное исключение, которое можно обозвать "аппаратным". Или не? Какие еще подобные исключения можно словить в "юзер-спейсе", которые не обрабатывала операционная система, а просто "вбросила"? И это не просто стёб, и не censored. Тут дело разработок. Просто скажи - вот они (список), или скажи "я погорячился". Прощу меня понять - мне важна суть, а не прав ты или не прав. Вообще на второе забей! Я триста раз могу ошибаться, но я тут чтобы получать достоверную инфу. Без обид! |
Сообщ.
#24
,
|
|
|
#AC и #BOUND (последнее давно не используется)
|
Сообщ.
#25
,
|
|
|
В плане дизайна обработки ошибок я бы сейчас рекомендовал использовать std::optional (иногда std::variant) и новенький std::expected.
Ну и именно исключения оставить на тот случай, когда совсем никак без них не обойтись (см. первое сообщение Qraizerа) А самое главное не забывать про т.н. гарантии безопасности исключений, введенные Саттером (или возможно Абрахамсом, но лично я впервые прочел у Саттера). Ну базовая, строгая и гарантия отсутствия исключений. Это достаточно легко гуглится, не вижу смысла тут писать подробности. Без понимания этих принципов будет очень сложно (невозможно?) написать качественный код с использованием исключений. |
Сообщ.
#26
,
|
|
|
Цитата D_KEY @ Бери выше. Без этих принципов писать надёжный код вообще вряд ли получится, и без разницы, исключения или нет и на каком языке пишешь. Без понимания этих принципов будет очень сложно (невозможно?) написать качественный код с использованием исключений. |
Сообщ.
#27
,
|
|||||||||||||||||||||||||||
|
Majestio, если заглянуть в winnt.h, то там их вагон и маленькая тележка, штук 60. Не все они аппаратные, но все низкоуровневые. Типа STATUS_GUARD_PAGE_VIOLATION, STATUS_BREAKPOINT, STATUS_STACK_OVERFLOW итп. Конкретно аппаратных немного:
|
Сообщ.
#28
,
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
P.S. У x86/64 исключений больше.
1) The UD2 instruction was introduced in the Pentium Pro processor. 2) Processors after the Intel386 processor do not generate this exception. 3) This exception was introduced in the Intel486 processor. 4) This exception was introduced in the Pentium processor and enhanced in the P6 family processors. 5) This exception was introduced in the Pentium III processor. |
Сообщ.
#29
,
|
|
|
Цитата Qraizer @ STATUS_FLOAT_DIVIDE_BY_ZERO Деление значения с плавающей запятой на 0,0. STATUS_FLOAT_OVERFLOW Превышение максимальной положительной экспоненты с плавающей запятой. STATUS_FLOAT_UNDERFLOW Превышение модуля наименьшей отрицательной экспоненты с плавающей запятой. STATUS_FLOATING_RESEVERED_OPERAND Использование зарезервированного формата с плавающей запятой (недопустимое использование формата). Вот эти 4 это просто флаги в fpu.StatusWord и как реальные Exception они не генерируются. Т.е. они софтверные и выбрасываются после проверки флага в регистре после fpu инструкции. |
Сообщ.
#30
,
|
|
|
Цитата Qraizer @ Majestio, если заглянуть в winnt.h, то там их вагон и маленькая тележка, штук 60. Не все они аппаратные, но все низкоуровневые. Типа STATUS_GUARD_PAGE_VIOLATION, STATUS_BREAKPOINT, STATUS_STACK_OVERFLOW итп. Конкретно аппаратных немного: Ну, положим, проверку деления на нуль можно делать, и нужно ... Но где и как делать проверку остального, к примеру STATUS_ILLEGAL_INSTRUCTION. Я даже не знаю - смогу ли я такое сгенерировать специально! Ну может быть какими-то ассемблерными вставками ... и то, не уверен. |
Сообщ.
#31
,
|
|
|
Цитата Majestio @ Но где и как делать проверку остального, к примеру STATUS_ILLEGAL_INSTRUCTION. Я даже не знаю - смогу ли я такое сгенерировать специально! Ну может быть какими-то ассемблерными вставками ... и то, не уверен. На С++ еще интринсики есть. Можно инструкции генерировать без asm вставок. А вот некоторые либы могут быть откомпилированы с использованием либо sse4+ либо на avx/avx256/avx512/avx10.2 (последнее в ближайшем будущем). И вот тут у проца может начаться несварение этих инструкций. Для sse4+ нужен минимум i5 (на i3 или pentium их нет), а для avx256 уже минимум i7. Но это только по линейке интелов. У амд свои приколы с этим с есть свои специфические инструкции. |
Сообщ.
#32
,
|
|
|
Цитата macomics @ Ты не учитываешь, что это WinAPI, а не конкретная процессорная архитектура. На MIPS, например, FLOAT OVERFLOW является вполне себе дефолтовым поведением после reset. К тому же не стоит забывать о всяких там _control87(), _controlfp() и __control87_2() Вот эти 4 это просто флаги в fpu.StatusWord и как реальные Exception они не генерируются. Т.е. они софтверные и выбрасываются после проверки флага в регистре после fpu инструкции. |
Сообщ.
#33
,
|
|
|
Цитата D_KEY @ В плане дизайна обработки ошибок я бы сейчас рекомендовал использовать std::optional (иногда std::variant) и новенький std::expected. Ну и именно исключения оставить на тот случай, когда совсем никак без них не обойтись (см. первое сообщение Qraizerа) Пасиба! Вот эта тема как-то пролетела мимо меня. Оставлю пока это тут чтобы потом почитать. |
Сообщ.
#34
,
|
|
|
Цитата Majestio @ Цитата D_KEY @ В плане дизайна обработки ошибок я бы сейчас рекомендовал использовать std::optional (иногда std::variant) и новенький std::expected. Ну и именно исключения оставить на тот случай, когда совсем никак без них не обойтись (см. первое сообщение Qraizerа) Пасиба! Вот эта тема как-то пролетела мимо меня. Оставлю пока это тут чтобы потом почитать. Статья про optional только вроде. Если есть возможность использовать новые стандарты и компиляторы, то советую не забыть и про std::expected |
Сообщ.
#35
,
|
|
|
Цитата Qraizer @ На MIPS, например, FLOAT OVERFLOW является вполне себе дефолтовым поведением после reset. Не видел Windows для MIPS |
Сообщ.
#36
,
|
|
|
Зачем вы вообще говорите про WinAPI, мы вроде в "общих вопросах", кажется, тут обычно мы говорим про стандарт, а если переходить на ОС и архитектуры, то нужно как-то затрагивать как минимум несколько наиболее востребованных.
|
Сообщ.
#37
,
|
|
|
D_KEY, ну кто ж виноват, что аппаратные исключения неСтандартизированы. Сам подумывал перенести, но контекст потеряется. Та и куда? Вот Majestio счас кинет не менее корявый пример на сигналах и что, опять переносить?
Добавлено macomics, Win32 вышел под x86, Alpha, MIPS, PPC и какой-то VLIW, не помню какой. Постепенно набор платформ сокращался, т.к. они уходили с рынка, вытесняясь Intel и AMD. Последней ушла вроде бы Alpha, зато на какое-то время к ним добавился Itanium. |
Сообщ.
#38
,
|
|
|
Цитата Qraizer @ Win32 вышел под x86, Alpha, MIPS, PPC и какой-то VLIW, не помню какой. Вот под PPC видел, но это когда было... 64-битных сейчас и нету под другие, кроме x86_64, но я может и не встречал. Цитата Qraizer @ зато на какое-то время к ним добавился Itanium. Это же тот же Intel (хотя и другая архитектура) |
Сообщ.
#39
,
|
|
|
Цитата Qraizer @ D_KEY, ну кто ж виноват, что аппаратные исключения неСтандартизированы А я так до конца и не понял, откуда тут взялась тема аппаратых исключений Вроде вопрос об исключениях в C++ и вообще дизайне обработки ошибок. |
Сообщ.
#40
,
|
|
|
А куда от них деться-то? Тема вечная. Я надеялся, что тут она не всплывёт, всё ж о Чистом С++ тут, но коль уж всплыла, а Стандартными методами о ней не поговорить, то либо так, либо раскидать тред по нескольким разделам и темам. Не думаю, что так было бы удобнее, пусть уж в одном месте будет.
|
Сообщ.
#41
,
|
|
|
Цитата Qraizer @ А куда от них деться-то? Тема вечная. Я надеялся, что тут она не всплывёт, всё ж о Чистом С++ тут, но коль уж всплыла, а Стандартными методами о ней не поговорить, то либо так, либо раскидать тред по нескольким разделам и темам. Не думаю, что так было бы удобнее, пусть уж в одном месте будет. Qraizer, не, всё-таки ты какими-то неведомыми путями мой вопрос "прибил" к аппаратным исключениям. Ну да, с твоей подачи я попробовал этот ваш вендовый SEH, да работает: #include <iostream> #include <windows.h> void div_function() { int d = 1; d--; int p = 10/d; } int main() { __try { div_function(); } __except (EXCEPTION_EXECUTE_HANDLER) { std::cout << "There's been an exception!" << std::endl; DWORD exceptionCode = GetExceptionCode(); switch (exceptionCode) { case STATUS_ACCESS_VIOLATION: std::cout << "Memory access error!" << std::endl; break; case STATUS_INTEGER_DIVIDE_BY_ZERO: std::cout << "Error: division by zero (integer)!" << std::endl; break; case STATUS_FLOAT_DIVIDE_BY_ZERO: std::cout << "Error: division by zero (float)!" << std::endl; break; default: std::cout << "Unknown error: " << exceptionCode << std::endl; break; } } return 0; } Но на самом деле меня интерсовал только вот такой вопрос: Цитата Majestio @ В рамках программирования "здорового человека" нужно немного поправить сигнатуру функции. А именно проверять b на нуль. И принимать одно из решений, учитывая тему топика: 1) Выкидывать свое исключение, оставив сигнатуру функции как есть 2) Либо поступить как поступают в других ЯП типа Rust/Dart - возвращать не int, а Result. Который, для упрощения, есть структура с полями "валидное_значение?" и "значение". Ну если не структура, пусть будет std::tuple. Не важно, главное принцип - "видишь деление, проверь". Тоже самое касается и остальных мест, где могут быть проблемсы. После обсуждения у меня появилось три варианта: #include <iostream> #include <optional> #include <stdexcept> bool safe_divide_1(int numerator, int denominator, double& result) noexcept { if (denominator == 0) return false; result = static_cast<double>(numerator) / denominator; return true; } std::optional<double> safe_divide_2(int numerator, int denominator) noexcept { if (denominator == 0) return std::nullopt; return static_cast<double>(numerator) / denominator; } double safe_divide_3(int numerator, int denominator) { if (denominator == 0) throw std::runtime_error("Division by zero!"); return static_cast<double>(numerator) / denominator; } int main() { // первый вариант double result1; if (safe_divide_1(10, 0, result1)) { std::cout << "Result 1: " << result1 << std::endl; } else { std::cout << "Error 1: Division by zero!" << std::endl; } // второй вариант auto result2 = safe_divide_2(10, 0); if (result2) { std::cout << "Result 2: " << *result2 << std::endl; } else { std::cout << "Error 2: Division by zero!" << std::endl; } // третий вариант try { double result3 = safe_divide_3(10, 0); std::cout << "Result 3: " << result3 << std::endl; } catch (const std::exception &e) { std::cout << "Error 3: " << e.what() << std::endl; } return 0; } Которые функционально выполняют всё тоже самое, я имею ввиду именно в этом коде. И по количеству строк, и примерно по количеству буквов, да и по читаемости - они все примерно равны. Хотелось бы какой-то синтетический пример, где обработка исключений делала бы код более компактным, чем использование кодов возврата, но не в ущерб читаемости. |
Сообщ.
#42
,
|
|
|
Цитата Majestio @ Что ты имел в виду, это хорошее пояснение, но я отвечал на это:Qraizer, не, всё-таки ты какими-то неведомыми путями мой вопрос "прибил" к аппаратным исключениям. ... Но на самом деле меня интерсовал только вот такой вопрос: Цитата Majestio @ В рамках программирования "здорового человека" нужно немного поправить сигнатуру функции. А именно проверять b на нуль. И принимать одно из решений, учитывая тему топика: 1) Выкидывать свое исключение, оставив сигнатуру функции как есть 2) Либо поступить как поступают в других ЯП типа Rust/Dart - возвращать не int, а Result. Который, для упрощения, есть структура с полями "валидное_значение?" и "значение". Ну если не структура, пусть будет std::tuple. Не важно, главное принцип - "видишь деление, проверь". Тоже самое касается и остальных мест, где могут быть проблемсы. Цитата Majestio @ Разве я что-то сделал не так? Давай ты решишь (в рамках сабжа!!!) другую задачу: 1) Задается произвольная матрица целых чисел размерностью 3x3 2) Одно из чисел (пусть одно) получит значение "нуль". 3) Нужно получить матрицу-результат = каждое значение исходной матрицы деленное на минимальное значение чисел из матрицы (что явно не есть нуль, но быть может) Нужно словить (это просто) когда очередной член матрицы делится на минимальное значение матрицы (равное нулю) - и самое главное, словить в расчетах первое деление на нуль. И вывести стек вызовов. Добавлено P.S. Последующее Цитата Qraizer @ явно показывает моё нежелание продолжать эту тему, но послеЯ правильно прочитал. Ты предлагаешь мне схавать аппаратное исключение? Это оффтоп темы Цитата Majestio @ я вообще потерялся в твоих желаниях, ибо оно вообще никоим боком не относится к теме с матрицами. 1) Есть сетевые ошибки, масса 2) Есть ошибки устройств I/O, часто 3) Крайний случай - тупо ошибки RAM, нечасто но видели Я тебе предлагаю это схавать! =) |
Сообщ.
#43
,
|
|
|
Цитата Qraizer @ Разве я что-то сделал не так? Ты перевел разговор на аппаратные исключения. Я кагбэ поддержал. Но ловить деление на нуль можно и обычной предварительной проверкой ... с последующей обработкой ... исключений или кодов возврата. Цитата Qraizer @ я вообще потерялся в твоих желаниях, ибо оно вообще никоим боком не относится к теме с матрицами. А вот и нет Мне чуйка подсказывает, что бóльшую красоту обработка ошибок в виде выброса и последующей обработки исключений - покажет при использовании ООП, а не при обычном структурном программировании. В плане примера ООП, в виде темы матриц мне показался самым примитивным и понятным даже школьнику. Вот такой мой довод. Сорян если я был слишком неточен в выражении своих вопросов |
Сообщ.
#44
,
|
|
|
Цитата Qraizer @ P.S. Я тем не менее с удовольствием посмотрел бы на транслятор сигналов POSIX в C++EH хотя бы краем глаза. Ну можно замутить отдельную тему для общего участия. Но я бы принял участие только при одном условии - использование "Шаблонов проектирования банды четырех" и "Принципов СОЛИДА". Ибо просто "а давайте навелосипедим кроссплатформенную шляпу-обёртку на С++" меня как-то не вдохновляет. Причину для себя понимаю - прикладной надобности у меня щя нет по этой теме, соответственно как-то автоматически включается "тонкое искусство пофигизма". А с ним бороться не айс! |
Сообщ.
#45
,
|
|
|
Цитата Majestio @ Ну да, с твоей подачи я попробовал этот ваш вендовый SEH, да работает: Можно ещё заменить ExceptionFilter. Те перехватить/заменить мессэдж бокс типа "..инструкция по адресу ... обратилась по адресу ... Память не может быть Read". Для этого используется "::SetUnhandledExceptionFilter". В результате можно получить всю необходимую диагностику. Однако, в отличии от многочисленных блоков __try / __except обнаружение места исключения будет дольше и сложнее. |
Сообщ.
#46
,
|
|
|
Ну а по теме ... запилил две синтетические реализации паттерна "Фасад":
на кодах возврата #include <iostream> class SubsystemA { public: int operationA() noexcept { // Имитация успешной операции return 0; // 0 - успех } }; class SubsystemB noexcept { public: int operationB() { // Имитация ошибки return -1; // -1 - ошибка } }; class Facade { private: SubsystemA subsystemA; SubsystemB subsystemB; public: int performOperations() noexcept { if (subsystemA.operationA() != 0) { return -1; // Ошибка в A } if (subsystemB.operationB() != 0) { return -2; // Ошибка в B } return 0; // Успех } }; int main() { Facade facade; int result = facade.performOperations(); if (result == 0) { std::cout << "The operation was successful." << std::endl; } else { std::cout << "An error occurred: " << result << std::endl; } return 0; } на обработках исключений #include <iostream> #include <stdexcept> class SubsystemA { public: void operationA() { // Имитация успешной операции // Ничего не происходит } }; class SubsystemB { public: void operationB() { // Имитация ошибки throw std::runtime_error("Error in SubsystemB"); } }; class Facade { private: SubsystemA subsystemA; SubsystemB subsystemB; public: void performOperations() { subsystemA.operationA(); // Успех subsystemB.operationB(); // Может выбросить исключение } }; int main() { Facade facade; try { facade.performOperations(); std::cout << "The operation was successful." << std::endl; } catch (const std::runtime_error& e) { std::cout << "An error occurred: " << e.what() << std::endl; } return 0; } Благодаря тому, что оба варианта примитивно-простые, особого преимущества второго варианта перед первым я не наблюдаю. По компактности и читаемости оба варианта примерно равны. Вопрос знатокам Какие можно внести правки для усложнения, чтобы показать, что вариант с обработкой исключений стал более красивым (компактным, читаемым, эффективным) по сравнению с вариантом на кодах возврата? Да и, собственно, просьба - внесите и покажите! |
Сообщ.
#47
,
|
|
|
Цитата Majestio @ Какие можно внести правки для усложнения, чтобы показать, что вариант с обработкой исключений стал более красивым (компактным, читаемым, эффективным) по сравнению с вариантом на кодах возврата? Да и, собственно, просьба - внесите и покажите! 1) Увеличение глубины вложенности классов. Как в виде наследования, так и в виде просто дополнительных объектов классов. (Это к удобству использования второго варианта) 2) Варианты исправления ошибки и перезапуска операции (допустим ошибка сигнализирует о timeout/retry). (Это к применимости первого варианта) 3) Как в первом случае найти точное место возникновения ошибки (не к минусам, а к необходимости усложнения обработки: не равноценные примеры)? |
Сообщ.
#48
,
|
|
|
Цитата macomics @ 3) Как в первом случае найти точное место возникновения ошибки (не к минусам, а к необходимости усложнения обработки: не равноценные примеры)? Ну тут просто. Создается enum со всеми видами ошибок. Другое дело, если один и тот же вид ошибок может быть в разных местах. Но и это решается - вместо числового кода возврата возвращается структура, включающая сам код, и макро __FILE__, __LINE__. Что-то типа: #include <iostream> #include <optional> #include <string> struct ErrorInfo { int errorCode; std::string fileName; int lineNumber; }; std::optional<ErrorInfo> performOperation(bool shouldFail) { if (shouldFail) { return ErrorInfo{404, __FILE__, __LINE__}; // Код ошибки, имя файла и номер строки } return std::nullopt; // Успешное выполнение, ошибки нет } int main() { auto result = performOperation(true); // Изменяем на false для успешного выполнения if (result) { std::cout << "Error: Code " << result->errorCode << ", File: " << result->fileName << ", Line: " << result->lineNumber << std::endl; } else { std::cout << "The operation was successful." << std::endl; } return 0; } Другое дело, что без стека вызовов, тут часто будет недостаточно инфы для однозначного определения причины. Но это уже другая тема. В принципе это же касается и варианта с использованием исключений, а-ля: #include <iostream> #include <optional> #include <string> #include <stdexcept> struct ErrorInfo { int code; std::string file; int line; }; class CustomException : public std::runtime_error { public: CustomException(const std::string& message, const std::string& file, int line) : std::runtime_error(message), errorInfo{1, file, line} {} const ErrorInfo& getErrorInfo() const { return errorInfo; } private: ErrorInfo errorInfo; }; std::optional<ErrorInfo> riskyOperation() { // Симулируем ошибку и выбрасываем исключение с информацией о месте выброса throw CustomException("Something went wrong!", __FILE__, __LINE__); } int main() { try { riskyOperation(); } catch (const CustomException& e) { // Обработка ошибки std::optional<ErrorInfo> error = e.getErrorInfo(); if (error) { std::cout << "Error Code: " << error->code; std::cout << ", File: " << error->file; std::cout << ", Line: " << error->line; std::cout << ", Message: " << e.what() << std::endl; } } return 0; } |
Сообщ.
#49
,
|
|
|
Цитата Majestio @ А оно надо? Ты ж не забывай о главном правиле дизайна библиотек: если нет никаких причин предпочитать одно решение остальным, его следует отдать на откуп пользователям библиотеки. Какие можно внести правки для усложнения, чтобы показать, что вариант с обработкой исключений стал более красивым (компактным, читаемым, эффективным) по сравнению с вариантом на кодах возврата? Да и, собственно, просьба - внесите и покажите! |
Сообщ.
#50
,
|
|
|
Цитата Qraizer @ А оно надо? Надо! Мы тут синтетические примеры решаем. И пытаемся (одно из) определить почему Qt почти отказались от обработки исключений в угоду другим способам обработки ошибок. Цитата Qraizer @ А оно надо? И второе, что О-Ч-Е-Н-Ь надо ... таки решить или обозначить тему топика. Где когда и что лучше?!! Дружище, давай "разговаривать и аргументировать" кодом. Пустые слова тут мало-мало ничего не решают по вопросу. Цитата Qraizer @ на откуп пользователям библиотеки А нам, пользователям библиотек, не безразлично! Особенно простой интерес - откуда у вас ноги растут, и почему оттуда? :-) |
Сообщ.
#51
,
|
|
|
Цитата Majestio @ Ну тут просто. Создается enum со всеми видами ошибок. Кажется, что ты опять игнорируешь std::expected Он таки получше std::optional будет для подобного. Добавлено Цитата Majestio @ И пытаемся (одно из) определить почему Qt почти отказались от обработки исключений в угоду другим способам обработки ошибок. Этому решению очень много лет. Тогда исключения были достаточно медленными и мне кажется плохо ложились на дизайн qt и его кодогенрацию и объектную модель. Т.е. считай исторические причины. ИМХО. Добавлено Цитата Qraizer @ Ты ж не забывай о главном правиле дизайна библиотек: если нет никаких причин предпочитать одно решение остальным, его следует отдать на откуп пользователям библиотеки. Ну да, можно пойти по пути asio с его функциями (для синхронного API), которые принимают опционально ссылку на код ошибки, а если не передаешь, то будет исключение. |
Сообщ.
#52
,
|
|
|
Цитата D_KEY @ Кажется, что ты опять игнорируешь std::expected Он таки получше std::optional будет для подобного. Ты прав. Я пока не дошёл до чтения фич std::expected. Но я исправлюсь, скорее всего завтра. Цитата D_KEY @ Этому решению очень много лет. Тогда исключения были достаточно медленными и мне кажется плохо ложились на дизайн qt и его кодогенрацию и объектную модель. Т.е. считай исторические причины. ИМХО. Похоже на то. Если я не ошибаюсь? Qt свой старт начал даже задолго до C++11. Т.е. еще на "ламповой" версии С++. |
Сообщ.
#53
,
|
|
|
Цитата D_KEY @ std::expected Мой первый блин комом - статья на Хабре. Читал сперва вдумчиво. Потом, в какой-то момент, мысль "поплыла", потому как параллельно возникла вторая - "начали городить огород". Дальше читал еще чуть бегло. Ну потом как обычно, какие-то лешие вышли из леса, русалки на ветвях уселись... Шютка Возникло уточнение второй мысли "начали городить огород могильной оградкой". В результате, как говорится, в сухом остатке - я понял, что чел начал типизировать ошибки просто по беспределу! Ну и что с того, что С++ поддерживает шаблонизацию? Это не значит, что нужно плодить сущности-ошибки просто от балды! И законный вопрос - на-хре-на?! Любая проектируемая система обладает своим набором уникальных состояний. Соответственно обладает своим набором и типовых ошибок. Да, часть множества ошибок и состояний может пересекаться от системе-к-системе. По типу "файл не найден", "ошибка записи" & etc. Но это не означает их идентичность! Везде же есть свой контекст. Что чел хотел сделать в статье, а тем более где-то там в глубинах статьи он еще присобачил динамический полиморфизм! Карл, динамический полиморфизм, для каста типов ошибок! Возможно мой "старт" стартанул некузяво. Но такая кухня мне явно не надо! Мой принцип давно и надолго - Бритва Оккама. А тут ни бритвы не видно, и Оккама давно не сидел. ... попробую поискать что-то более вменяемое |
Сообщ.
#54
,
|
|
|
Ну, человек поставил себе задачу, человек её решил и поделился решением. Решение наиуниверсальнейшее, так что почему бы и нет. Я бы вот не стал настолько заморачиваться, но положа руку на сердце, ведь именно за то мы и любим Плюсы, что можем наваять либу так, что пользоваться ею можно будет так же, как будто это языковое средство.
Скажем так: если тебе интересно, как оно устроено, то любая либа внутри выглядит страшно, но если тебе интересно лишь, как оно работает, то юзаешь и не паришься. |
Сообщ.
#55
,
|
|
|
А тебе не кажется, что это немотивированное усложнение гораздо простого вопроса? Ну если честно, Только если честно?
Добавлено Цитата Qraizer @ Решение наиуниверсальнейшее, так что почему бы и нет. Стоп! Ты тоже считаешь - что ошибки нужно типизировать? И что это реально даст профит?!!! |
Сообщ.
#56
,
|
|
|
Majestio, ну первый же пример из статьи понятен?
enum class MathError : unsigned char { ZeroDivision, NegativeNotAllowed }; std::expected<int, MathError> Bar(int a, int b) { if (b == 0) return std::unexpected(MathError::ZeroDivision); if (a < 0 || b < 0) return std::unexpected(MathError::NegativeNotAllowed); return a / b; } Существенная разница с optional тут в том, что ты не забываешь вид ошибки. Это близко к Rust'овскому Result<T, E>. А дальше он в статье о чем-то другом уже начал |
Сообщ.
#57
,
|
|
|
Цитата D_KEY @ Существенная разница с optional тут в том, что ты не забываешь вид ошибки. Я вид/тип ошибки могу успешно не забывать и кодом ошибки? И это считаю (пока) самым простым и изящным решением. Убеди меня что мне ну вот очень-очень нужно ошибки "типизировать" Ну убеди меня! |
Сообщ.
#58
,
|
|
|
Цитата Majestio @ Я вид/тип ошибки могу успешно не забывать и кодом ошибки? В optional не сможешь Цитата Majestio @ Убеди меня что мне ну вот очень-очень нужно ошибки "типизировать" Ну убеди меня! Ну можешь использовать std::expected<T, int> и использовать свои int'овые коды на здоровье Суть-то в том, что "обычный" код ошибки ты можешь проигнорировать случайно и т.п., а тут придется обработать. Собственно, мотивация Result<T, E> в Rust такая же. |
Сообщ.
#59
,
|
|
|
Цитата D_KEY @ что ты не забываешь вид ошибки Искусственное усложнение, детектед! Есть набор ошибок - этого для обработки хватает. А вот для анализа ... но это уже потом ... собирай их в группы как хочется. В свои эти "виды". Но зачем эту шляпу внедрять в код? Хватит номеров ошибок, не? Добавлено Цитата D_KEY @ Суть-то в том, что "обычный" код ошибки ты можешь проигнорировать случайно и т.п., а тут придется обработать. Собственно, мотивация Result<T, E> в Rust такая же. Т.е. я объявляю секцию catch или if-фю код возврата и ... сразу "случайно" забываю нахрена я это делаю?!! Мама, роди меня обратно. |
Сообщ.
#60
,
|
|
|
Цитата Majestio @ или if-фю код возврата или не объявляешь А компилятор молчит, к сожалению. Плюс даже если объявил, сможешь случайно обратиться к результату. А это исключено в std::expected или std::optional. Добавлено И я тебя не уговариваю, используй что хочешь. Страдать тебе потом самому |
Сообщ.
#61
,
|
|
|
D_KEY, хе-хе!
Я всегда за разумный компромисс. Подчёркиваю - разумный! Кодинг в стиле "на давай нафигачим 100500 строчек кода, которые в 3-5 местах нам помогут избежать ошибок" мне не нравится. В любом случае, когда ты из беты вылезаешь в релиз - ты каждую строчку кода анализируешь? Это закон. И если ты видишь, к примеру, игнор кода возврата ошибки, и ничего не делаешь (если нужно) - это чисто твои проблемы. Кстати это ещё один вопрос - а не кинуть ли исключение? И это в плюс в обработку ошибок/ситуаций исключениями. |
Сообщ.
#62
,
|
|
|
Так строчек кода больше не станет. Я не понимаю, о чем ты
|
Сообщ.
#63
,
|
|
|
Цитата D_KEY @ Так строчек кода больше не станет. Я не понимаю, о чем ты Ну так я выше давал ссылку на статью с Хабра. Там чел расписал как по его мнению правильно использовать std::expected - накидал портянку кода без какой-то предметной "нагрузки". Я об этом. Ну а так да, std::expected, если без извращений хорош. Ho ... Цитата D_KEY @ Суть-то в том, что "обычный" код ошибки ты можешь проигнорировать случайно и т.п., а тут придется обработать. А кто же меня заставит? Вот тебе пример, где я взял и не обработал, и ничего мне не пришлось, и получил х3 что: #include <iostream> #include <expected> #include <string> std::expected<double, std::string> safe_divide(double numerator, double denominator) { if (denominator == 0) { return std::unexpected("Division by zero"); } return numerator / denominator; } int main() { auto result1 = safe_divide(10, 0); std::cout << "Result: " << *result1 << std::endl; // Это обработка, которую я убрал //if (result1) { // std::cout << "Result: " << *result1 << std::endl; //} else { // std::cout << "Error: " << result1.error() << std::endl; //} return 0; } Просто std::expected позволяет тягать за собой код ошибки. Более "стандартно", нежели я как-то выше показывал вариант возврата структуры. Вот и все. |
Сообщ.
#64
,
|
|
|
Цитата Majestio @ Цитата D_KEY @ Суть-то в том, что "обычный" код ошибки ты можешь проигнорировать случайно и т.п., а тут придется обработать. А кто же меня заставит? Вот тебе пример, где я взял и не обработал, и ничего мне не пришлось, и получил х3 что Ну конкретно тут косяк дизайна std::expected (ИМХО). Если ты будешь использовать result.value(), то все будет как я писал. Ты не сможшь пойти дальше, если там нет значения, ты получишь исключение. Просто не используй * при работе с expected, кроме редких случаев, когда точно проверил, что там лежит. Я не знаю, зачем они так сделали. |
Сообщ.
#65
,
|
|
|
Цитата D_KEY @ Ну конкретно тут косяк дизайна std::expected (ИМХО). Если ты будешь использовать result.value(), то все будет как я писал. Ты не сможшь пойти дальше, если там нет значения, ты получишь исключение. Просто не используй * при работе с expected, кроме редких случаев, когда точно проверил, что там лежит. Я не знаю, зачем они так сделали. Ну вот и я о чём. Но, спасибо, учту. |
Сообщ.
#66
,
|
|
|
Цитата D_KEY @ Затем же, зачем operator[] в std::vector<> при живом-то std::vector<>::at(). Сам заюзал, не проверив, сам и отвечаешь. Я не знаю, зачем они так сделали. Добавлено К слову, отладочная STL от MS этот * не пропустит. Как и operator[] для векторов. В дебаге всё проверяется само, поэтому тормозит безбожно |
Сообщ.
#67
,
|
|
|
Цитата Qraizer @ Цитата D_KEY @ Затем же, зачем operator[] в std::vector<> при живом-то std::vector<>::at(). Сам заюзал, не проверив, сам и отвечаешь.Я не знаю, зачем они так сделали. Я могу понять, зачем так делат для vector. Но для нового класса, еще и предназначенного для ошибок, я бы спрятал беспроверочную версию за отдельным методом (желательно с уродским названием). |
Сообщ.
#68
,
|
|
|
Цитата D_KEY @ Я могу понять, зачем так делать для vector. Но для нового класса, еще и предназначенного для ошибок, я бы спрятал беспроверочную версию за отдельным методом (желательно с уродским названием). У меня есть предположение ... С++ никогда не позиционировался как язык программирования с "исключительной поддержкой безопасности". Даже вспомнить спичи про его предшественника Си - про него часто говорили мол это "высокоуровневый ассемблер". Вывод? Простой. С++ предоставляет сперва самые быстрые варианты обработки. И только потом "помогает" в случае х3 чего контролить процесс исполнения. Простой пример. Ты явно задаешь вектор из 16Гб элементов, явно и строго! Зачем тебе в этом случае контролить счётчик индексов, чтобы он не вышел за диапазоны? Для этого и нужны методы без проверок, они сокращают время исполнения. Но тебе ни кто не запрещает влепить операторы с контролем. Только в продакшене это кому-нить будет нужно?! |
Сообщ.
#69
,
|
|
|
После покрытия тестами – нет. Но покрытие тестами должно быть 100%. Дорого? Да.
Добавлено P.S. Дабы предотвратить избыточный флейм. Покрытие тестами не гарантирует отсутствия ошибок, оно гарантирует лишь поведение кода совпадающее с описанным. При этом описание не коррелирует с качеством реализованной архитектуры. Отсутствие ошибок проектирования обеспечивает верификация, которая опирается на тестирование как один из этапов своих процедур. Вот это по-настоящему дорого. И даже она не гарантирует совпадения спроектированного с ожидаемым, это задача валидации. Есть такая вся из себя супер-пупер безопасная и строгая Ада. Практически не используется. Флай-код обычно – это даже не куда как более развитые в этом отношении Плюсы, а абсолютно небезопасные и весьма лояльные к программерским сумасбродиям C и капелька ассемблера. Несложно догадаться, почему. |
Сообщ.
#70
,
|
|
|
Цитата Qraizer @ После покрытия тестами – нет. Но покрытие тестами должно быть 100%. Дорого? Да. Не совсем понял. В языках программирования нет понятия "покрытие тестами". Это есть в методах разработки и введения в эксплуатацию, не? |
Сообщ.
#71
,
|
|
|
Цитата Majestio @ А "продакш" разве есть? Вопрос в том, что изменяется, если отключить проверки. Ответ прост: безопасность не увеличивается, но может уменьшиться. Для "продакшна" важно, чтобы не уменьшилась, а это возможно лишь тогда, когда доказана их избыточность. Тесты на это ...ну, способны. В языках программирования нет понятия "покрытие тестами". |
Сообщ.
#72
,
|
|
|
Цитата Qraizer @ А "продакш" разве есть? Вопрос в том, что изменяется, если отключить проверки. Ответ прост: безопасность не увеличивается. Для "продакшна" также важно, чтобы не уменьшилась, а это возможно лишь тогда, когда доказана их избыточность. Тесты на это ...ну, способны. Согласен на 114%! Но это не отменяет того, что C++ (равно как и Си) ... сперва ратует за производительность, а уже во вторую очередь - за "безопасность" (которую и нужно шерстить тестами). Это я к чему? Везде во всех либах С++ "стремится" к производительности, и тут внезапно ДиКей запустил "желалку"! Мол, а "давайте все проверять по умолчанию!". И я тут против. Не для такого как "он" Си писались, "перестраховщик"! Добавлено Цитата Qraizer @ А "продакш" разве есть? Конечно! Его другое имя - "релиз". Т.е. всё то, что не дебаг. Издеваесся? |
Сообщ.
#73
,
|
|
|
Цитата Majestio @ Именно. Я вообще не понимаю назначение этих вот MISRA. Либо ты пишешь фигофину, надёжность которой по большому счёту ничего не решает. Тогда можешь для пущего эффекта натравить на неё MISRA и всякие там стат.анализаторы. Только зачем? Либо ты пишешь серьёзный продукт, и тогда будь добр обеспечить его надёжность. ИМХО проще взять Плюсы и задействовать их потенциал безотказного кодинга на полную. Кодревью будет достаточно, чтобы не прошляпить самые очевидные промахи, а с остальным разберётся функциональное тестирование. Для Cей или консервативных Плюсов придётся тестировать куда глубже, без модульных обойтись не получится, а обычный вчерашний выпускник онлайн курсов тестирования просто охренеет, поймёт, что его ничему не научили толком, и пойдёт запивать потраченные к₽ водкой. И опять же причём тут MISRA, непонятно.Везде во всех либах С++ "стремится" к производительности, и тут внезапно ДиКей запустил "желалку"! Мол, а "давайте все проверять по умолчанию!". И я тут против. Не для такого как ты Си писались, "перестраховщик"! Если ты обеспечиваешь качество, то какая разница, как оно написано. По факту MISRA требует отказаться от всего, ради чего C/C++ выбирают, ну и зачем тогда их выбирать? Пишите на Паскале или Питоне. Только от архитектурных и алгоритмических багов ни язык, ни MISRA вас всё равно не спасут. А если всё функциональные (хотя бы) тесты дают добро, то все проверки можно убрать и не париться. Добавлено Упомянутая выше Ада как раз и нафик никому не сдалась по этой причине. Да, строгая, да, безопасная. Компилер мало что пропустит, а остальное чекается в ран-тайм. Только толку от этого, если в коде программер вместо sin() случайно вкопипастил cos() и не поправил. Без тестирования всё равно не обойтись, и внезапно выясняется, что все ран-тайм чеки, жрущие до 75% ресурсов, уже не нужны, т.к. всё вылизано до идеала. А не отключишь, бо язык вот такой и больше никакой. |
Сообщ.
#74
,
|
|
|
Итог? Простой - ни какой язык программирования тебя не спасет от "кодинга по синьке" =) И не нужно ДиКею что-то требовать от Стандарта сверх меры его "компетенции".
Добавлено Цитата Qraizer @ Упомянутая выше Ада как раз и нафик никому не сдалась по этой причине. Да, строгая, да, безопасная. Компилер мало что пропустит, а остальное чекается в ран-тайм. Только толку от этого, если в коде программер вместо sin() случайно вкопипастил cos() и не поправил. Без тестирования всё равно не обойтись, и внезапно выясняется, что все ран-тайм чеки, жрущие до 75% ресурсов, уже не нужны, т.к. всё вылизано до идеала. А не отключишь, бо язык вот такой и больше никакой. +1 |
Сообщ.
#75
,
|
|
|
P.S. В MISRA вообще дохрена вредных правил. Вот быстро в голову пришло что-то с локальными переменными, чтобы они мол назывались не так, как глобальные. А то ай-яй-яй случайно можно заюзать не то, что думаешь заюзать. Та ёлки ж! индустрия 40 лет вынашивала правило всё локализовывать по максимуму как раз ради того, чтобы не влиять на глобальный контекст, когда оно не нужно. И тут приходят авторы правил MISRA и говорят вы все эти 40 лет были неправы. Ага, счас.
|
Сообщ.
#76
,
|
|
|
Qraizer, это не самое плохое! Хуже, когда работаешь в режиме "динамического ТЗ". У меня был такой опыт. Делал проект в районе 3-х лет, когда (по моим оценкам) он должен был исполнится в 3-4 месяца. Заказчик - "государство". Какие нахер правила, стандарты и прочее?! Приходит "очередной" директор, и всё начинается по-новой. Так что твой MISRA по сравнению ... самый красивый кросавчег!
|
Сообщ.
#77
,
|
|
|
Ну, меняющиеся ТЗ ничто не победит. Кроме денег. Новый директор, новые правила — новый ценник
|
Сообщ.
#78
,
|
|
|
Цитата Majestio @ Цитата D_KEY @ Я могу понять, зачем так делать для vector. Но для нового класса, еще и предназначенного для ошибок, я бы спрятал беспроверочную версию за отдельным методом (желательно с уродским названием). У меня есть предположение ... С++ никогда не позиционировался как язык программирования с "исключительной поддержкой безопасности". Даже вспомнить спичи про его предшественника Си - про него часто говорили мол это "высокоуровневый ассемблер". Вывод? Простой. С++ предоставляет сперва самые быстрые варианты обработки. И только потом "помогает" в случае х3 чего контролить процесс исполнения. Простой пример. Ты явно задаешь вектор из 16Гб элементов, явно и строго! Зачем тебе в этом случае контролить счётчик индексов, чтобы он не вышел за диапазоны? Для этого и нужны методы без проверок, они сокращают время исполнения. Но тебе ни кто не запрещает влепить операторы с контролем. Только в продакшене это кому-нить будет нужно?! Для вектора мне понятно, а вот для expected - нет. Мы вводим класс для типобезопасной обработки ошибки и он тут же нам подкидывает undefined behaviour на самой распространенной операции, которые незнающие люди будут использовать по умолчанию. Это в каком-то смысле обесценивает вообще наличие std::expected. Добавлено Цитата Majestio @ Везде во всех либах С++ "стремится" к производительности, и тут внезапно ДиКей запустил "желалку"! Мол, а "давайте все проверять по умолчанию!". И я тут против. Не для такого как "он" Си писались, "перестраховщик"! Ты мои слова перевираешь зачем-то. Во-первых, я согласен с политикой std::vector. Во-вторых, я пояснил, почему считаю это плохим дизайном именно для std:: expected. Лучше бы они вообще тогда не делали operator* и -> И именно отдельные методы с проверкой и без проверки, с нормальными именами. Добавлено Завязывай с переходом на личности. Это тематика таки Добавлено В общем, Expected из folly (либа от Facebook), выглядит приятнее. Ну и там чувствуется рука Александреску, который идею expected и продвигал. |
Сообщ.
#79
,
|
|
|
Цитата D_KEY @ Для вектора мне понятно, а вот для expected - нет. Мы вводим класс для типобезопасной обработки ошибки Неа, ты выдаешь желаемое за действительное. Новый способ обработки есть, типобезопасности обработки ошибок нет. И всего-то. Цитата D_KEY @ Ты мои слова перевираешь зачем-то. А вот и нет А кто выше писал о "типобезопасности"? Это по-любому влечет за собой проверки, или на стадии компиляции, или в рантайме, без разницы! Цитата D_KEY @ Завязывай с переходом на личности. Это тематика таки Какие мы нежные и чувствительные Но ок, за "перестраховщика" - прости, я не со зла! |
Сообщ.
#80
,
|
|
|
Цитата Majestio @ Новый способ обработки есть, типобезопасности обработки ошибок нет. И всего-то. Одна из главных мотиваций введение expected - сохранить фишку исключений, что ошибку нельзя проигнорировать неявно, но при этом сделать эту необходимость видимой. Это есть у Александреску, это есть даже в предложении std:: expected в стандарте. И при этом они тут же это фактически нарушают. Постепенно, думаю, будет распространенной практикой отказ от разыменования (ну разве что с разрешением его использовать немедленно после проверки в одном блоке) и использование других методов (value, value_or, and_then, or_else, transform и пр.) Добавлено Вот цитата из предложения в стандарт (из мотивационной части): Цитата Error visibility: It takes the best of the exception and error code. It’s visible because the return type is expected<T, E> and users cannot ignore the error case if they want to retrieve the contained value. А вот нет, могут. Это у Александреску и folly нельзя, а в std можно проигнорировать, просто ставим -> или * и получаем UB. Но да, ниже у них написано, что * без проверки сделали для производительности, но что мешало для этого сделать отдельный метод - загадка. |
Сообщ.
#81
,
|
|
|
Цитата Majestio @ типобезопасности обработки ошибок нет Хотя нет, я тут погорячился - типобезопасность таки есть, безопасности нет Цитата D_KEY @ Но да, ниже у них написано, что * без проверки сделали для производительности Ну вот про это я сразу подумал, хотя не читал. Это как-то само собой ... |
Сообщ.
#82
,
|
|
|
Цитата Majestio @ Цитата D_KEY @ Но да, ниже у них написано, что * без проверки сделали для производительности Ну вот про это я сразу подумал, хотя не читал. Это как-то само собой ... Да, конечно. Только в данном случае это удар по самой мотивации введения std:: expected (в отличие от примера с вектором). Цитату я привел. Достаточно было ввести отдельный метод с говорящим названием. Ну ещё одна вещь, которую будут зубрить в плюсах и спрашивать на собесах |
Сообщ.
#83
,
|
|
|
Цитата D_KEY @ Достаточно было ввести отдельный метод с говорящим названием. Остаётся только гадать. Мое предположение такое, что работа именно с указателями в С/C++ является местом более частых багов. Поэтому они решили "о вот вам еще!" Сообщения были разделены в тему "Ошибки IO не ловятся исключениями" |