Здравствуйте, AlexRK, Вы писали:
vsb>>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.
ARK>Главная проблема в том, что исключения создают дополнительный скрытый поток управления, который никак в коде не виден.
ARK>Это не было бы проблемой, если бы гарантировалась атомарность всех изменений на некотором участке, на котором предположительно может вылететь исключение. То есть, если исключения нет — коммит, если есть — роллбек. При таком раскладе программа всегда остается в валидном состоянии и все инварианты сохраняются в любой момент времени. Увы, у STM есть фундаментальная проблема — ввод-вывод (на данный момент приемлемого решения этой проблемы не найдено).
ARK>Таким образом, исключения предлагают избавление от лапши проверок, давая взамен потенциально некорректное состояние программы. ARK>Коды возврата гарантируют видимость всех путей исполнения и отсутствие неожиданных нарушений инвариантов, но генерируют много бойлерплейта. ARK>Что лучше — хрен знает.
По-моему невалидное состояние после исключения это достаточно редкая штука. Можете привести какие то часто встречающиеся случаи таких состояний в managed языке (Java/C# например)? Я только искусственные примеры могу придумать, в реальности я такого не видел (может не замечал).
Здравствуйте, smeeld, Вы писали:
vsb>>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.
S>Исключения это очень медленно и размашисто, по сравнению с if(func()!=0) err(); S>Оправдывает себя только для случаев, когда исключением можно обработать глобальные S>сбой или ошибку, с или остановкой приложения, или его, в некотором смысле, полным перезапуском, S>с какими другими входными данными и иным способом. S>Короче, это как тяжёлая артиллерия, и применять это средство необдуманно, нерационально-чревато, S>потерей производительности.
Насколько медленней? Насколько это может сказаться в работе программы? Я вот не могу себе представить сценария, где программа будет пытаться миллион раз открыть несуществующий файл и её производительность будет страдать от медленных исключений.
Здравствуйте, x-code, Вы писали:
XC>Интересный вопрос — что с этим делать? С одной стороны, и от исключений отказываться не хочется, с другой — как бы исхитриться сделать так чтобы исключение, брошенное из функции, превращалось в код возврата, если обработчик исключения для данного исключения не предусмотрен? Компилятор в принципе может это отследить?
Да, что делать — непонятно. Монады разве что действительно прикручивать.
XC>Например, сделать какой-то особый оператор (вместо throw) который был бы чем-то средним между throw и return: он мог бы бросать исключение, если ранее в какой-то глобальной таблице блок try выставил флаг, что данный тип исключения обрабатывается; а если флага нет, то возвращал бы свой аргумент как код возврата.
Здравствуйте, vsb, Вы писали:
vsb>По-моему невалидное состояние после исключения это достаточно редкая штука. Можете привести какие то часто встречающиеся случаи таких состояний в managed языке (Java/C# например)? Я только искусственные примеры могу придумать, в реальности я такого не видел (может не замечал).
Я бы сказал, что это не только не редкая штука, а наоборот — повсеместная.
Любой метод, изменивший некоторое поле класса и после этого выкинувший исключение, уже оставил объект в промежуточном состоянии.
Другой вопрос — всегда ли это приводит к катастрофическим последствиям? Понятное дело, что нет.
Здравствуйте, smeeld, Вы писали:
S>"Правильно реализованные", это как? Смотрел как они реализованы в ELF+gcc/clang/solarisstudio.
Вот так:
The second scheme, and the one implemented in many production-quality C++ compilers, is a table-driven approach. This creates static tables at compile time and link time that relate ranges of the program counter to the program state with respect to exception handling.[18] Then, if an exception is thrown, the runtime system looks up the current instruction location in the tables and determines what handlers are in play and what needs to be done. This approach minimizes executive overhead for the case where an exception is not thrown. This happens at the cost of some space, but this space can be allocated into read-only, special-purpose data sections that are not loaded or relocated until an exception is actually thrown.
S>Простой if(err) return -1; сработает на порядок быстрее, чем throw x; }catch(){...}
Ты что при каждом вызове функции исключение выбрасываешь?
Обычно они летают редко. И когда не летают, вообще никаких дополнительных расходов нет.
При этом проверка кодов возврата тормозит всегда.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, vsb, Вы писали:
vsb>Насколько медленней? Насколько это может сказаться в работе программы? Я вот не могу себе представить сценария, где программа будет пытаться миллион раз открыть несуществующий файл и её производительность будет страдать от медленных исключений.
Тут как всегда — кто о чем, а сиплюсплюсник о скорости. Корректность ПО, производительность разработки? Не, не слышал.
Насчет замедления я где-то читал постмортем о разработке игры для PS3, там чувак приводил цифры насчет исключений — по его словам, замедление может быть до 30% (поэтому они от исключений отказались).
Но это, понятное дело, на слабоватом железе.
А это уже зависит от стечения обстоятельств.
C медленней на порядок это, конечно, перебульонил малость,
но в остальном происходит поиск по нескольким массивам в секциях,
отмаппенного в память процесса, объектника: в .eh_frame ищутся CIE для
нахождения FDE, из FDE попадаем в секцию расширение (.gcc_except_table для gcc)
с массивом LSDA структур, по ним находим пойнтеры к type_info вызовом функции personality,
которая своя для каждого языка. type_info находятся в другой секции, при совпадении, находим
в массивах call site-ов из текущего LSDA пойнтеры для landing pad.
Соответствующими значениями из call site-ов заполняется структура Unwind_Context, далее происходит
вторая фаза, при которой всё повторяется сначала, только не для поиска совпадений,
а очищения фреймов стека вызовом той же personality. После чего управление передаётся на обработку загрузкой
оформленного контекста. В инете полно инфы, описал тут так для того, чтоб масштаб событий, происходящих после throw, был заметен.
Здравствуйте, AlexRK, Вы писали:
vsb>>По-моему невалидное состояние после исключения это достаточно редкая штука. Можете привести какие то часто встречающиеся случаи таких состояний в managed языке (Java/C# например)? Я только искусственные примеры могу придумать, в реальности я такого не видел (может не замечал).
ARK>Я бы сказал, что это не только не редкая штука, а наоборот — повсеместная. ARK>Любой метод, изменивший некоторое поле класса и после этого выкинувший исключение, уже оставил объект в промежуточном состоянии. ARK>Другой вопрос — всегда ли это приводит к катастрофическим последствиям? Понятное дело, что нет.
Интересно, я неявно стараюсь все изменения записывать в локальные переменные и присваивать в поля скопом, уже когда вся работа выполнена, а присваивания уж точно не выкинут исключение. Да, есть такая проблема и нужно писать exception-safe код. Вопрос — сложно ли это?
WH>The second scheme, and the one implemented in many production-quality C++ compilers, is a table-driven approach. This creates static tables at compile time and link time that relate ranges of the program counter to the program state with respect to exception handling.[18] Then, if an exception is thrown, the runtime system looks up the current instruction location in the tables and determines what handlers are in play and what needs to be done. This approach minimizes executive overhead for the case where an exception is not thrown. This happens at the cost of some space, but this space can be allocated into read-only, special-purpose data sections that are not loaded or relocated until an exception is actually thrown.
Всякие статейки в инетах читаем? Похвально, но посоветовл бы заглянуть в реальные
исходники, и узреть как оно на самом деле там наворочено.
Здравствуйте, smeeld, Вы писали:
S>Всякие статейки в инетах читаем? Похвально, но посоветовл бы заглянуть в реальные S>исходники, и узреть как оно на самом деле там наворочено.
Я реально измерял производительность.
Коды возврата слили.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, vsb, Вы писали:
vsb>Интересно, я неявно стараюсь все изменения записывать в локальные переменные и присваивать в поля скопом, уже когда вся работа выполнена, а присваивания уж точно не выкинут исключение. Да, есть такая проблема и нужно писать exception-safe код. Вопрос — сложно ли это?
Молодец. Только у меня имеются противоположные результаты. Может
просто дизассемблером по бинарникам и по исходникам компиляторов прошвырнёмся,
и определим, что будет исполнятся медленней, а что быстрее.
Здравствуйте, smeeld, Вы писали:
S>Молодец. Только у меня имеются противоположные результаты. Может S>просто дизассемблером по бинарникам и по исходникам компиляторов прошвырнёмся, S>и определим, что будет исполнятся медленней, а что быстрее.
Покажи класс. Я скажу, где у тебя ошибка.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Сугубое ИМХО.
Одна из серьезных проблем, которую практически не поминают — это парадигма обработки исключений.
По сути это событийное программирование.
Ведь исключения могут генерироваться в проге где угодно.
И обработка их ведется совершенно в другом месте. Причем программист этим практически не управлял.
И вот получается, что в одной проге смешаны как минимум две, а то и три парадигмы: ООП+событийное (и очень часто процедурное).
И если нормальную работу худо-бедно проектируют, то аварии не проектируют совсем.
И обработка исключений часто появляются как заплатки над "правильным" кодом.
Надо отметить, что в новом стандарте управляемость исключений СУЩЕСТВЕННО улучшилась.
Текущее исключение можно явным образом сохранить, передать в нужную функцию, заново кинуть в нужном месте.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, WolfHound, Вы писали:
WH>Покажи класс. Я скажу, где у тебя ошибка.
Ок. Код, обрабатывающий ошибку возвратом из функции:
Такой кодик
#include <iostream>
int func(int a){
int b=0;
if(a) b=-1;
return b;
};
int main(){
int a=10;
if( func(a)!=0) std::cout<<"BAD \n";
else std::cout<<" Ok \n";
};
Между двумя листингами есть только одно существенное отличие- вызов функции __cxa_throw(), в коде с исключениями,
который происходит после подготовки работы функцией __cxa_allocate_exception. Эти функции
принадлежат libstdc++. __cxa_throw -это просто кроличья нора, куда проваливаемся по самое не балуйся.
Дальше приведу ссылки на исходники GCC-С++, где расписаны основные функции, в которые, так или иначе, попадает управление после вызова __cxa_throw.
Кстати, из последней мы не возвращаемся, а, загрузкой контекста стекового фрейма, оказываемся на том месте, которые сответствуют
определённым catch. В какой псоледовательности происходит вся работа можно отследить отладчиком, стартуя c break point __cxa_throw.
Ссыли
Здравствуйте, smeeld, Вы писали:
S>Ок. Код, обрабатывающий ошибку возвратом из функции:
Зевая. Перечитай вторую половину сообщения 10 раз. Re[4]: Какие у исключений проблемы?
Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.
где он явно говорит о использовании исключений вместо проверок корректности исполнения
функций значениями возврата из функций, что подразумевает таки использование исключений
в каждой функции, требующей проверки.
Во вторых, процетрирую свой стартовый посыл, в котором как раз говорил про Ваше "не из каждой функции"
Оправдывает себя только для случаев, когда исключением можно обработать глобальные
сбой или ошибку, с или остановкой приложения, или его, в некотором смысле, полным перезапуском,
с какими другими входными данными и иным способом.
WH>Ты не первый и даже не десятый кто пытается доказать что-то не корректными тестами.
А тестами можете размахивать на праздных академических симпозиумах и конференциях.
При разработки кода для дикого и ответственного продакшена, ориентироваться надо
на тонкое знание реализации тех или иных используемых систем, по которому определять характер
использования этих систем.
Здравствуйте, smeeld, Вы писали:
S>А тестами можете размахивать на праздных академических симпозиумах и конференциях. S>При разработки кода для дикого и ответственного продакшена, ориентироваться надо S>на тонкое знание реализации тех или иных используемых систем, по которому определять характер S>использования этих систем.
Главное при этом не забыть, что в 99.999% случаев код возврата содержит значение "всё хорошо".
А значит на практике всё то чем ты тут размахиваешь просто тает на фоне постоянных if (errorcode != ok)...
Что бы получить реалистичный тест сделай пять функций, которые друг друга вызывают. Запрети компилятору их инлайнить.
Вызови их 100000 раз. А на 100001 скажи, что всё плохо.
И сравни время выполнения.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, vsb, Вы писали:
vsb>Интересно, я неявно стараюсь все изменения записывать в локальные переменные и присваивать в поля скопом, уже когда вся работа выполнена, а присваивания уж точно не выкинут исключение. Да, есть такая проблема и нужно писать exception-safe код. Вопрос — сложно ли это?
Я тоже так пишу. Саттер/Абрахамс в свое время прочистили мозги на этот счет.
Надо только помнить, что есть 3 уровня безопасности по исключениям.
Базовую гарантию (отсутствие утечек памяти, незакрытых хэндлов, неразлоченных мьютексов) обеспечить можно практически всегда.
Сильную — не всегда (если принимать во внимание сопутствующие накладные расходы, которые могут быть слишком большими), но и не всегда это так уж нужно.
Ну и у меня уровень exception safety, обеспечиваемый функцией — это отдельная строчка в Doxygen-комментарии перед ней, наряду с thread safety (даже макрос специальный для Doxygen есть).
Здравствуйте, x-code, Вы писали:
XC>Здравствуйте, AlexRK, Вы писали:
ARK>>Главная проблема в том, что исключения создают дополнительный скрытый поток управления, который никак в коде не виден.
XC>Да, согласен. XC>Вызываешь функцию, и не знаешь — возвратится ли она здесь или где-то вообще неизвестно где...
Ну конкретно в C++ (начиная с С++11) есть оператор nothrow(выражение), который позволяет определить, может ли выражение внутри него бросить исключение (основываясь на декларации nothrow).
К сожалению, нельзя им пометить блок кода и как-то заставить бросать ошибку компиляции, если встретилось выражение, которое может бросить, но и то хлеб — потенциально опасные функции можно проверить на nothrow заранее (полезно в шаблонах, когда не знаешь, что тебе подсунут).