Здравствуйте, Andrew S, Вы писали:
AS>Всем привет.
AS>Ситуация следующая — разрабатывается небольшой слой кросс-платформенной абстракции (io, sync, mt etc). В который раз возник вопрос, что в каких ситуациях использовать — исключения и коды возврата ошибок.
AS>Если есть ссылки критерии, best practice или что-нибудь подобное — будет супер.
boost.asio
все функции в двух экземплярах, одни бросают, другие (у которых есть параметр boost::system::error_code & ec) — не бросают.
Пример:
Здравствуйте, Andrew S, Вы писали:
AS>Хочется формальных критериев, когда надо только исключения, когда только результат, когда нужно, например, вот такое раздвонение интерфейса.
Имхо, нету таких формальных критериев.
Все зависит от пользователя: для одного неудача — штатная ситуация, а для другого — фатальная ошибка.
Например, должна ли бросать функция открытия файла, если файл не найден?
Если у тебя список файлов, и тебе нужно попробовать их все по порядку и использовать первый нашедшийся, то неудача с одним файлом — это штатный эпизод перебора файлов, а фатальная ошибка — это если вообще ни одного файла нет.
А если у тебя всего лишь одно имя файла, то невозможность его открыть — это фатальная ошибка.
И всё это разнообразие может быть у пользователя в одной функции загрузки конфигов.
Так что правильный путь, имхо — это дать возможность пользователю самому решить, что ему нужно, например, как делает boost.asio (можно и по-другому делать).
Совсем формальным критерием может служить ответ на вопрос
"возможно ли дальнейшее выполнение если ..."
Например
1. в конструкторе обертки над мютексом, мы не смогли создать именованный мютекс. В этом случае лучше кинуть ексепшен, потому что объект повиснет в невалидном состоянии. Если этого не делать , то пользователь класса может и забыть проверить валидное создание объекта.
2. Объект поток не смог открыться и вернул напримр false в методе stream.open(), после этого любая попытка прочесть из него или записать должны кидать эксепшены.
Ну и опять же все зависит от сценария использования.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[5]: api design: return code or exception - formal criteri
Здравствуйте, Andrew S, Вы писали:
... J>>Так что правильный путь, имхо — это дать возможность пользователю самому решить, что ему нужно, например, как делает boost.asio (можно и по-другому делать).
AS>Но, имхо, не для каждого интерфейса. Либо делать 2 разных интерфейса с возможность move-upgrade одного в другой... AS>Вот этого бы не хотелось, на самом деле.
Если модификация api невозможна, то можно такие варианты рассмотреть:
// Дано:
result api::f1(); // все nothrow
result api::f2();
result api::f3();
// использование
checked_result r(error_code1 | error_code2);
r = api::f1();
r = api::f2();
r = api::f3();
Объект checked_result в операторе присваивания проверяет аргумент на присутствие error_code1 или error_code2, и в случае обнаружения кидает исключение. Либо просто использует что-то вроде 'if(FAILED(hr)) throw ...'.
Другой вариант — опять nothrow api, + легкие обертки-функторы:
remove_file remove(std::nothrow); // любые ошибки игнорируются
std::for_each(v.begin(), v.end(), remove);
//...
// not_found трансформируется в исключение,
// остальные ошибки просто возвращаются.
remove_file remove(not_found);
result r = remove(filename);
if(r == access_denied)
{
//...
}
Re[25]: api design: return code or exception - formal criter
Юрий Жмеренецкий:
ЮЖ>Тогда объясни — _зачем_ нужно принудительно проверять предусловия и каким-то образом (отличным от assert) реагировать на их нарушение?
Вставлять проверку предусловия перед каждым вызовом функции неудобно — так весь код ими будет пестрить. assert не годится для проверки в release-версии. Там, где принудительная проверка даёт заметный overhead, нужно предоставлять версию функции без проверки — разрешать компромисс между производительностью и обладанием контроля над ошибками — это дело пользователя функции.
ЮЖ>Я тебе говорю про другое — предусловия нужно проверять с помощью assert'ов, а не throw/abort.
Ещё раз: assert-ы не работают в релизе. А некоторые программы имеют неприятное свойство глючить после стадии тестирования — у конечного пользователя.
НИ>>А в случае внезапного abort несохранённые данные вообще будут безвозвратно утеряны. Хорошенькое дельце, ничего не скажешь.
ЮЖ>И сохранить их получится со 100% гарантией, ага. В валидном состоянии. Так?
Нет, но вероятность сохранить хоть что-то есть.
ЮЖ>Только проблема в том что, как минимум нет гарантии, что на момент обнаружения логической ошибки в системе не произошло фатальных изменений (мало ли почему не выполнены предусловия).
Такой гарантии вообще никогда нет.
ЮЖ>Банально может произойти сохранение мусора
Этот вариант не исключён, но иначе у пользователя вообще нет шансов восстановить данные. Я категорически не согласен с тем, что пользователю надо отказывать в такой возможности — даже в виду незначительной вероятности порчи уже сохранённой информации (сохранение желательно вести во временный файл). К тому же в случае многопоточной программы внезапная смерть всех потоков также может быть весьма разрушительной.
ЮЖ>>>Разумый выход — снижение количества потенциально опасных мест, которые могут привести к остановке (defect -> fault). При большом количестве дефектов (сам по себе дефект не всегда приводит к остановке) появляется проблема с их идентификацией, которую ты озвучил. Вместо строительства подсистемы ловли багов логичнее попытаться снизить общее количество мест их возникновения. Вот здесь как раз можно эксплуатировать ценное свойство пред/пост условий — выводимость из контекста. Не нужно выполнять повторную проверку, там, где ее выполнение выводимо из контекста. И никакой ошибки в этом случае не возникнет.
НИ>>Выводимо оно из контекста или нет, решает программист. Решение программиста может быть неправильным.
ЮЖ>Опять 25. Неправильным оно может быть только в одном случае — если он ранее нарушил предусловие.
Он мог неверно оценить постусловия предыдущих вычислений.
ЮЖ>Если программист не хочет ошибаться — пусть зовет на помощь компилятор — во многих случаях он может помочь.
В некоторых — не может. О них-то тут и идёт речь.
НИ>>Программист может быть уверен (ошибочно), что эти предусловия всегда соблюдаются. ЮЖ>Он должен быть уверен что они соблюдаются. И сам в свою очередь должен их соблюдать.
Я не понял, мы о простых смертных программистах говорим или о роботах, которые никогда не ошибаются?
НИ>>Зачем тогда он станет их проверять? А если и проверит, то что будет делать в случае обнаружения несоответствия (в release-сборке)? ЮЖ>+1. Их не надо проверять, они уже проверены и выполняются по определению.
Кем проверены? Тебя послушать, так и assert-ы не нужны: идеальный программист всюду идеально подгонит постусловия предыдущих вычислений под предусловия последующих, и все будут жить долго и счастливо. Прямо утопия какая-то получается.
ЮЖ>>>Чуть-чуть усложнили здесь, чуть-чуть усложнили там
НИ>>Как часто происходит такое усложнение?
ЮЖ>Везде при подобном коде:
ЮЖ>
/// \pre x > 10, p != NULL
ЮЖ>void f(int x, int* p)
ЮЖ>{
ЮЖ> assert(x > 10);
ЮЖ> assert(p);
ЮЖ> if(x > 10)
ЮЖ> throw std::invalid_argument("f::x"); // В клинических случаях добавляется запись в лог, etc.
ЮЖ> // Тип исключения в принципе не так важен, но это по
ЮЖ> // сути логическая ошибка (std::logic_error).
ЮЖ> // Иногда можно увидеть std::runtime_error - но это уже похоже на саботаж
ЮЖ> if(!p)
ЮЖ> throw std::invalid_argument("f::p");
ЮЖ> //...
ЮЖ>}
Я пока вижу сложность только в использовании подобных функций внутри nothrow функций.
НИ>>Отмечу, что генерация исключения не лишает нас возможности произвести тот же abort в любое другое время после обработки ошибки. И даже если в процессе раскрутки стека мы получим какие-нибудь неразрушительные ошибки вроде утечки памяти, это не будет иметь никакого значения, коль скоро программа всё равно будет аварийно завершена.
ЮЖ>abort хотя бы защитит от возможных деструктивных изменений в данных во время раскрутки
В многопоточной программе действие, лишь наполовину выполненное другим потоком, также может быть деструктивным.
ЮЖ>>>PS: Кроме того, всю (а не 'какую-то') необходимую информацию можно получить куда проще — она (вместве с контекстом) доступна перед вызовом.
НИ>>И куда её девать? Логировать?
ЮЖ>Незнаю, она тебе была нужна =)
Мне-то она нужна именно в логе, но запись в него должна вестись по мере возникновения ошибки, а не на всякий случай при каждом вхождении в какую-то функцию. Чтобы узнать, кто позвал функцию, где обнаружился сбой, нужно контролировать либо вхождения в функции, либо выходы из них по исключению. В случае с немедленным вызовом abort второй способ отбрасывается. Теперь возникает вопрос: как контролировать вхождения? Вместо жирных логов можно использовать TLS-хранилище для запоминания стека вызовов, но это всё равно может быть накладно с точки зрения производительности, и данное хранилище придётся использовать на всех уровнях.
ЮЖ> // Вот тут доступен весь контекст. Нужно в лог — пишем в лог... ЮЖ> // + Не нужно протаскивать никакие данные через исключения.
Если функция может быть вызвана из нескольких мест в программе, то о доступности всего контекста можно говорить лишь при наличии сведений о стеке вызовов. Сама по себе функция понятия не имеет о том, кто её вызывает, а при поиске бага эта информация важна.
Re: api design: return code or exception - formal criteria
Здравствуйте, Andrew S, Вы писали:
AS>PS Свое мнение по данному вопросу у меня имеется В данном случае нужны именно формальные критерии, чтобы каждый конкретный случае не вызывал одинаковых вопросов.
О, я придумал формальный критерий: всегда должны использоваться исключения, потому что они обеспечивают гарантию непродолжения в случае ошибки, не давая програмисту неявно ошибиться.
Отказ от исключений и работа через коды ошибок (в любом виде, будь то возвращаемые значения или out-параметры, как в Boost.Asio) — это ручная оптимизация, которая должна применяться адекватно, как и любая другая оптимизация, т.е. после профилирования и т.п.
Это на уровне приложения.
На уровне библиотеки: поскольку библиотеке неведомо, как она будет применяться, то она должна предоставить оба интерфейса — один для 99% процентов случаев, давая наиболее чистый код пользователя, а другой — для тех мест, где надо все заоптимизировать (иначе пользователю будет неудобно — ему в этих местах придется пользоваться другой библиотекой).
Здравствуйте, byleas, Вы писали:
B>Проще объединить, что и предложили в N2838: B>
int func(int param, std::error_code& ec = throws())
Не, ну тогда жечь, так жечь...
Можно завести такой классец, который, например, умеет хранить внутри себя исключение.
При этом можно его сделать таким странным, что он может его бросать в случае если передали специально сконструированный экземпляр.
Скажем так:
int func(int param, const IExceptionsTransmitter& et = throws())
{
if(param == invalid_param_value){
et = std::invalid_argument("param");
return EINVAL;
}
// ... do the work
}
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[8]: api design: return code or exception - formal criteri
Здравствуйте, Юрий Жмеренецкий, Вы писали:
ЮЖ>Здравствуйте, IROV.., Вы писали:
IRO>>А что делать если f1 возвращает значение? делать через out параметры? не всегда удобно/красиво
ЮЖ>Здесь два варианта: ЮЖ>1) Код ошибки при этом возвращается через out параметры (что встречается редко). ЮЖ>2) Ошибкой считается некоторое состояние возвращаемого значения. ЮЖ>Что здесь лучше/хуже/удобней/красивей?
Главная проблема с возвращаемыми значениями — это то, что они игнорируются по умолчанию (в отличие от исключений).
Поэтому если код написан безопасно по отношению к исключениям, то у нас есть автоматическая гарантия, что ни одна ошибка не будет пропущена — против лома нет приема, если исключение вылетело, то оно будет лететь, пока его не поймаешь явно.
С возвращаемыми же значениями в варианте два пропустить ошибку легче легкого, ее ведь даже не видно — зовешь себе функции, как будто они все возвращают void.
В этом смысле первый вариант мне кажется лучшим — по крайней мере, ты говоишь явно, что собираешься обрабатывать ошибки без исключений: ты объявляешь переменную с кодом ошибки и потом пихаешь ее во все вызовы, из которых ты не хочешь получить исключение.
Конечно, тут тоже легко пропустить обработать ошибку, особенно при копи-пейсте, но это уже не будет настолько невидимым, как в варианте два — по крайней мере, код ошибки присутствует во всех вызовах явно, и то, что его не обработали — видно.
Здравствуйте, slava_phirsov, Вы писали:
_>Здравствуйте, jazzer, Вы писали:
J>>К чему я и клоню: разработка без исключений — это оптимизация.
_>Не согласен. Это просто другой подход к разработке. Где-то выше уже поминался iostream, в котором исключения не используются, а используются флаги.
Во-первых, ты мешаешь все в кучу.
Там флаги состояния потока (которые устанавливаются функцией setstate!). А это значит, что реализуется конечный автомат, и к исключениям это никакого отношения не имеет.
А вот как сигнализировать о том, что пользователь пытается выполнить операцию, которая недопустима для текущего состояния или переводит поток в плохое состояние (failbit) — тут можно использовать и исключения, и коды.
Во-вторых, неплохо бы такие утверждения проверять и не верить им на слово.
Открываем стандарт и чуть ли не в каждом абзаце видим:
If ......, calls setstate(failbit), which may throw ios_base::failure (27.4.4.3).
что же означает may throw?
Это означает, что iostream можно сконфигурировать как для бросания исключений, так и молчаливого возврата.
Смотрим 27.4.4 и видим такую пару функций:
A mask that determines what elements set in rdstate() cause exceptions to be thrown.
_>ИМХО, в C++ надо десять раз подумать, прежде чем кидаться исключениями.
Пока что я не услышал внятной аругментации, по которой отказ от исключений не должен рассматриваться в ряду прочих оптимизаций или телодвижений по поддержке странных платформ.
_>Слышал такой аргумент: "А если в клиентском коде не обрабатываются коды ошибок?". ОК, а если автор клиентского кода засунет функцию генерирующую исключение в try-catch с пустым блоком catch — лучше будет? Ну да, это немного потруднее чем просто проигнорировать код ошибки.
Именно что потруднее, но главное даже не это, главное, что пустой catch очень хорошо виден, и code review такой код просто не пройдет.
_>Но для копипэйстных кодеров (нормальный-то кодер ИМХО не станет игнорировать код ошибки)- не проблема, и лично я такую стратегию наблюдал неоднократно.
Против копипейстных кодеров, кроме code review, вообще приема нет, это диверсанты.
Также я видел море кода нормальных кодеров (включая себя), которые забывали проверять коды ошибок.
Еще раз: проблема кодов ошибок — это их невидимость.
Вызов функции, которая ничего не возвращает, в коде никак не отличается от вызова функции, которая возвращает код ошибки.
Вот это на 4.5 порядка сложнее отследить, чем написанный большими русскими буквами пустой catch.
Ну и обработка кодов ошибок очень сильно захламляет код, в отличие от кода, написанного с исключениями, в котором основная линия исполнения программы (т.е. когда не происходит ошибок) незамусорена и легко читается (== легко поддерживается).
Здравствуйте, Andrew S, Вы писали:
AS> Assert ровно такое же средство уведомления о нарушении контракта, как и исключение.
То же что и в предыдущем посте.
Исключение это средство ссобщить об ошибке рограммы, Assert об ошибке програмиста.
например в псевдокоде.
std::copy(It begin, It end, it2 begin2);
Существует задокументированный контракт что begin и end обязаны принадлежать одному множеству. В случае невыполнения этого контракта у результатом будет UB ( могло быть и исулючение, но это накладывает на реализацию жесткие ограничения).
В этом случае, хорошим тоном от разработчиков библиотеки, будет проверить и подсказать в отладочной сборке нарушение контракта. Но это не к коей мере не обработка ошибки, это только подсказка пользователям библиотеки о неправильном использовании.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[2]: api design: return code or exception - formal criteri
Здравствуйте, minorlogic, Вы писали:
M>Совсем формальным критерием может служить ответ на вопрос M>"возможно ли дальнейшее выполнение если ..."
M>Например
M>1. в конструкторе обертки над мютексом, мы не смогли создать именованный мютекс. В этом случае лучше кинуть ексепшен, потому что объект повиснет в невалидном состоянии. Если этого не делать , то пользователь класса может и забыть проверить валидное создание объекта.
Это несколько "искусственный" критерий — так получится что исключения нужно кидать только из конструкторов и операторов. В более обтекаемой форме этот критеририй звучит как невозможность выполнения постусловий. Но с применением такого подхода есть тонкости — он больше подходит при проектировани "сверху вниз" — от требований к реализации. В случае с api/фреймворком ситуация усложняется т.к. набор требований и сценариев использования как правило недостаточно полон.
M>2. Объект поток не смог открыться и вернул напримр false в методе stream.open()
Наличие метода open у потока — это нарушение SRP. Поток это интерфейс, которым могут обладать различные сушности — вот у них такой метод можут существовать. Либо такая сущность может существовать косвенно — вариант с 'stream open_filestream(const string& name)'. Здесь и потенциальных ошибок меньше.
M>после этого любая попытка прочесть из него или записать должны кидать эксепшены.
Можно просто в предусловия вызовов методов добавить произведенный вызов open.
M>Ну и опять же все зависит от сценария использования.
+1
Re[18]: api design: return code or exception - formal criter
Здравствуйте, Николай Ивченков, Вы писали:
ЮЖ>>Смысл принципа: не хочу — не плачу. Если у меня из контекста использования автоматически вытекает выполнения предусловия — зачем мне нужна проверка "внутри"? На всякий случай? Нарушение в том, что в этом случае у меня нет рычага для ее отключения. А насколько это бьет по производительности — совершенно неважно.
НИ>Ну, если брать это за аксиому...
Просто ты предполагаешь что, в обсуждаемом случае, производительность — главный аспект. Это не всегда так, см. ниже.
ЮЖ>>Ты хочешь сказать что аллокатор кидает out_of_range?
НИ>Нет, я хочу сказать, что, устранив throw, соответствующий проверке предусловия pos <= str.size(), мы всё равно не получим конструктор, не генерирующий исключений (для которого компилятор потенциально мог бы сгенерировать более оптимальный код).
Ок, теперь понял о чем ты. Меня больше интересует поведение клиента в том случае, когда он не смог обеспечить выполнение предусловий. Могу ли я в этом случае (как реализующий функцию) переводить программу в состояние 'сгенерированно исключение'? Клиент не выполнил своего контракта — как я могу ожидать от него готовности к такой ситуации? Если он готов обработать такую ситуацию — почему он сразу не выполнил предусловия?, он ведь заинтересован в результате. Возбудив исключение — я просто замаскирую дефект, логическую ошибку программиста (клиента). Логическую — не потому что logic_error, а потому что это именно ошибка в логике, неспособность понять формулу 'если P то Q'. Такие ошибки надо исправлять (не допускать), а не обрабатывать. Есть еще некоторые проблемы, но они уже являются производными от этой.
Re[24]: api design: return code or exception - formal criter
Здравствуйте, Николай Ивченков, Вы писали:
НИ>Юрий Жмеренецкий:
ЮЖ>>>>Нельзя рассматривать исключения как замену assert-у. Кроме того, если быть последовательным, тогда нужно таким же образом проверять все предусловия
НИ>>>Какое этому обоснование?
ЮЖ>>Иначе, при таком подходе (очень плохом, имхо), нельзя обнаружить все ошибки (логические).
НИ>Очень туманное объяснение.
Тогда объясни — _зачем_ нужно принудительно проверять предусловия и каким-то образом (отличным от assert) реагировать на их нарушение? Для решения каких проблем это используется?
НИ>>>Хорошо, у пользователя программа упала по abort. Разработчику нужно пофиксить багу. Как ты предлагаешь выяснять примерную причину ошибки
ЮЖ>>Это другой вопрос (решаемый в принципе).
НИ>Этот вопрос напрямую связан с выбором между throw и немедленным abort. Где-то, конечно, можно вести очень подробные логи — тыкать логирование в каждую функцию, — но это изврат.
Повторю — нужно снижать количество мест, содержащих дефекты — а не плодить их.
НИ>Или под "решаемый в принципе" подразумевается тщательный review всего кода?
Подразумевается только то что написано.
Я тебе говорю про другое — предусловия нужно проверять с помощью assert'ов, а не throw/abort. Проблем, возникающих при использовании abort, меньше чем при использовании throw. Только в большинстве случаев и это не является оправданием для использования этого метода.
ЮЖ>>Разница между возбуждением исключения и abort в этом случае — нет гарантий, что размотка стека не приведет к инициации других ошибок
НИ>И что?
См. ниже.
ЮЖ>>нет гарантий что все данные останутся в порядке
НИ>А в случае внезапного abort несохранённые данные вообще будут безвозвратно утеряны. Хорошенькое дельце, ничего не скажешь.
И сохранить их получится со 100% гарантией, ага. В валидном состоянии. Так? Только проблема в том что, как минимум нет гарантии, что на момент обнаружения логической ошибки в системе не произошло фатальных изменений (мало ли почему не выполнены предусловия). Банально может произойти сохранение мусора (хорошо если не перезапись оригинала).
ЮЖ>>нет гарантий что обработчик исключений не содержит багов
НИ>Если так рассуждать, то пользователю вообще не следует запускать программу, т.к. изначально нет никакой гарантии, что она не содержит багов.
Верно. Для той категории багов о которой мы говорим (логических ошибок). Ведь изначально вместе с 'throw logic_error' явно постулируется факт наличия дефектов (нарушение предусловий).
ЮЖ>>abort выводит программу из состояния 'баг' с минимальными последствиями.
НИ>Потеря несохранённых данных — это, по-твоему, минимальные последствия?
Ответил выше.
НИ> Меньший объём кода -> меньшая вероятность ошибок.
С этим полностью согласен.
ЮЖ>>>>Только для полного счастья, повторюсь, необходимо проверять все предусловия и все постусловия. А это приведет к катастрофическому снижению производительности.
НИ>>>Нужно найти разумный компромисс.
ЮЖ>>Разумый выход — снижение количества потенциально опасных мест, которые могут привести к остановке (defect -> fault). При большом количестве дефектов (сам по себе дефект не всегда приводит к остановке) появляется проблема с их идентификацией, которую ты озвучил. Вместо строительства подсистемы ловли багов логичнее попытаться снизить общее количество мест их возникновения. Вот здесь как раз можно эксплуатировать ценное свойство пред/пост условий — выводимость из контекста. Не нужно выполнять повторную проверку, там, где ее выполнение выводимо из контекста. И никакой ошибки в этом случае не возникнет.
НИ>Выводимо оно из контекста или нет, решает программист. Решение программиста может быть неправильным.
Опять 25. Неправильным оно может быть только в одном случае — если он ранее нарушил предусловие. Если программист не хочет ошибаться — пусть зовет на помощь компилятор — во многих случаях он может помочь.
ЮЖ>>Опять тот же вопрос — т.е. программист обладает достаточной квалификацией, для того чтобы писать код, устойчивый к исключениям в любых местах (+ допускаются неизбежные дополнительные затраты по ресурсам), а проверить предусловия для вызова функции он не в состоянии?
НИ>Программист может быть уверен (ошибочно), что эти предусловия всегда соблюдаются.
Он должен быть уверен что они соблюдаются. И сам в свою очередь должен их соблюдать.
НИ>Зачем тогда он станет их проверять? А если и проверит, то что будет делать в случае обнаружения несоответствия (в release-сборке)?
+1. Их не надо проверять, они уже проверены и выполняются по определению. Поэтому второй вопрос отпадает, logic_error как и abort становится ненужным.
ЮЖ>>Чуть-чуть усложнили здесь, чуть-чуть усложнили там
НИ>Как часто происходит такое усложнение?
Везде при подобном коде:
/// \pre x > 10, p != NULLvoid f(int x, int* p)
{
assert(x > 10);
assert(p);
if(x > 10)
throw std::invalid_argument("f::x"); // В клинических случаях добавляется запись в лог, etc.
// Тип исключения в принципе не так важен, но это по
// сути логическая ошибка (std::logic_error).
// Иногда можно увидеть std::runtime_error - но это уже похоже на саботажif(!p)
throw std::invalid_argument("f::p");
//...
}
НИ>Отмечу, что генерация исключения не лишает нас возможности произвести тот же abort в любое другое время после обработки ошибки. И даже если в процессе раскрутки стека мы получим какие-нибудь неразрушительные ошибки вроде утечки памяти, это не будет иметь никакого значения, коль скоро программа всё равно будет аварийно завершена.
И throw и abort не защитят от порчи данных до обнаружения. abort хотя бы защитит от возможных деструктивных изменений в данных во время раскрутки, + защитит от возможной 'обработки' — catch(const std::logic_error&), правда в этом случае бить по пальцам нужно стальной и заточенной линейкой.
ЮЖ>>PS: Кроме того, всю (а не 'какую-то') необходимую информацию можно получить куда проще — она (вместве с контекстом) доступна перед вызовом.
НИ>И куда её девать? Логировать?
Незнаю, она тебе была нужна =) Единственное что необходимо — на ее основе надо сделать выводы о возможности дальнейшего выполнения. Т.е. это самый 'верх' — то место где проверяются предусловия.
/// \pre arg > 0void f(int arg);
/// \pre arg nonevoid my_main(int arg)
{
// Я хочу вызвать функцию 'f'...if(arg <= 0) // <- Несоответстиве(*) предусловий для my_main#arg и f#arg - сигнал о необходимости проверки.
{
// Вот тут доступен весь контекст. Нужно в лог - пишем в лог...
// + Не нужно протаскивать никакие данные через исключения.
log_error(arg);
return;
}
// Я обеспечил выполнение предусловий - выполнил свою сторону контракта.
f(arg);
}
// * Буквальное несоответстиве не всегда приводит к проверке
Re[4]: api design: return code or exception - formal criteri
Здравствуйте, gear nuke, Вы писали:
GN>Здравствуйте, Andrew S, Вы писали:
AS>>вот такое раздвонение интерфейса.
GN>В последней ревизии немного другой подход: GN>
Q>>В чем собственно проблема? Или я неправильно понял? J>1) нельзя к указателю на функцию привязать
А интересно, а откуда такая необходимость, с учетом того, что сигнатура функции содержит С++ типы
Здравствуйте, Andrew S, Вы писали:
AS>>>Всем привет.
AS>>>Ситуация следующая — разрабатывается небольшой слой кросс-платформенной абстракции (io, sync, mt etc). В который раз возник вопрос, что в каких ситуациях использовать — исключения и коды возврата ошибок.
J>>все функции в двух экземплярах, одни бросают, другие (у которых есть параметр boost::system::error_code & ec) — не бросают.
AS>Ух, сильно. Нужно ли это для каждого метода — вот вопрос. Мое мнение, что для каждого метода это лишнее, и сам же буст этому пример — например, трединг, алгоритмы... AS>Хочется формальных критериев, когда надо только исключения, когда только результат, когда нужно, например, вот такое раздвонение интерфейса.
Дело в том, что судя по тому что ты озвучил, это библиотека. Причем вещей достаточно низкоуровневых.
Соответственно ты, по идее, не знаешь (и сам об этом говоришь) в каком контексте будешь использовать.
Так что разумно реализовывать оба варианта.
Если ломы делать сразу оба — оттолкнись от проекта для которого в первую очередь эта библиотека пойдет.
Там должна быть стратегия обработки ошибок, делай в первую очередь тот вариант который удачно впишется в неё.
Но делай имея ввиду, что придется делать и второй вариант.
ЗЫ "раздвонение интерфейса" возьму на вооружение
Re[4]: api design: return code or exception - formal criteri
Здравствуйте, jazzer, Вы писали:
J>К чему я и клоню: разработка без исключений — это оптимизация.
Не согласен. Это просто другой подход к разработке. Где-то выше уже поминался iostream, в котором исключения не используются, а используются флаги. ИМХО, в C++ надо десять раз подумать, прежде чем кидаться исключениями. Слышал такой аргумент: "А если в клиентском коде не обрабатываются коды ошибок?". ОК, а если автор клиентского кода засунет функцию генерирующую исключение в try-catch с пустым блоком catch — лучше будет? Ну да, это немного потруднее чем просто проигнорировать код ошибки. Но для копипэйстных кодеров (нормальный-то кодер ИМХО не станет игнорировать код ошибки)- не проблема, и лично я такую стратегию наблюдал неоднократно.
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Re[5]: api design: return code or exception - formal criteri
ЮЖ>Effects:
ЮЖ>The current thread blocks until ownership can be obtained for the current thread.
ЮЖ>Postcondition:
ЮЖ>The current thread owns *this.
ЮЖ>Throws:
ЮЖ>boost::thread_resource_error if an error occurs.
ЮЖ>При такой формулировке (общей для всех lock types), с отсутствующими предусловиями — возбуждение исключения единственно верный вариант.
Не согласен. Приведенная формулировка концепта никоим образом не влияет на то, что именно должен делать lock холдер в случае нарушения _своего_ предусловия. Оунер может быть и рекурсивным. Assert ровно такое же средство уведомления о нарушении контракта, как и исключение. Вот только объект lock холдер после него в релизе останется в невалидном состоянии. Т.е. тут совсем другая ситуация, чем в случае с handle holder (ака File), который даже после нарушения контракта все равно останется валидным.
Здравствуйте, slava_phirsov, Вы писали:
_>Здравствуйте, jazzer, Вы писали:
J>>К чему я и клоню: разработка без исключений — это оптимизация.
_>Не согласен. Это просто другой подход к разработке.
Другой подход — это следствие, причем весьма заметное, т.к. влияет на дизайн в целом, и не редко, в худшую сторону. Как минимум, контракты становятся "толще", и соответственно повышается вероятность ошибки в самом контракте, не говоря уже о реализации и использовании.
Можно услышать такой аргумент против — при использовании исключений приходится отдавать дань за удобство в виде abstraction penalty, а именно — снижение производительности. Этот аргумент может быть актуальным, а может быть и нет. Реальное снижение производительности может быть вызвано неправильным использованием — логика на исключениях, либо использованием в контекстах, в которых стоимость производимых операций одного порядка со стоимостью затрат на поддержку исключений. Оторванные от контекста заявления вроде 'исключения снижают производительность' — не более чем сферокони. Для проверки так это или нет — нужно смотреть конктерную ситуацию. Плюс, адекватное сравнение производительности двух подходов выполнить достаточно сложно.
Так же на нераспростроненность исключений (косвенно это можно назвать 'другим подходом') сказывается соседство с С и наличие огромного количества legacy кода без использования исключений. Некоторые особенности "модульной" системы С++ (осутствие единого, полностью специфицированного ABI), таже не поощеряют исключения.
_>... ИМХО, в C++ надо десять раз подумать, прежде чем кидаться исключениями.
Безусловно, использование исключений требует некоторой дисциплины. Но осязаемых бенефитов в совокупности в результате больше, имхо.
Здравствуйте, Andrew S, Вы писали:
AS>Если есть ссылки критерии, best practice или что-нибудь подобное — будет супер.
Вот тебе пример. Есть библотека X, разработчики которой решили сделать парные функции, одни с поддержкой исключений, другие с поддержкой кода возврата. Появились пользователи библиотеки (программисты) которые перестали использовать функции с кодом возврата, а только фунции бросающие исключения. После появились программисты библиотеки, которые поняли, что каждый раз включать галку в студии останавливаться на исключении в отладчике не катит и вставили ассерт перед самим throw. После появились ещё более хитрожопые пользователи библиотеки, которые решили всё-таки ловить исключения через try-catch из-за чего стали появляться бесполезные ассерты перед каждым throw, т.е. ассерт перед throw не сигнализирующий ошибку, т.к. исключение впоследствии ловится и обрабатывается.
К чему это всё скажите вы? А к тому что иногда поддержка исключений библиотекой приводит к образованию некоторого пользовательского болота, организованного недобросовестными программистами использующими библиотеку, из которого вам впоследствии будет трудно выбраться.
Иногда лучше вообще не пользоваться исключениями и "заставлять" пользователя обрабатывать код возврата.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[2]: api design: return code or exception - formal criteri
Здравствуйте, Vain, Вы писали:
V>Здравствуйте, Andrew S, Вы писали:
AS>>Если есть ссылки критерии, best practice или что-нибудь подобное — будет супер.
[snip]
V>К чему это всё скажите вы? А к тому что иногда поддержка исключений библиотекой приводит к образованию некоторого пользовательского болота, организованного недобросовестными программистами использующими библиотеку, из которого вам впоследствии будет трудно выбраться. V>Иногда лучше вообще не пользоваться исключениями и "заставлять" пользователя обрабатывать код возврата.
А мне показалось, что лучше не ставить ассерты перед throw и недобросовестеыми тут выглядят как раз программисты библиотеки, сделавшие это.
The last good thing written in C was Franz Schubert's Symphony No. 9.
Re[14]: api design: return code or exception - formal criter
Здравствуйте, Andrew S, Вы писали:
AS>Тем не менее. Представим, что реализация делает именно то, что и должна. Изначальный вопрос звучал так — почему тут исключение, а не assert? И то, и то может быть использовано как сигнализатор нарушения контракта.
Нарушение контракта всегда является дефектом, независимо от того с чьей стороны произошло нарушение (клиент/реализация). Возникновение исключения (вообще, не рассматривая unique_lock)- не является дефектом — это невозможность выполнения постусловий при выполненных предусловиях. Состояние объекта, как таковое, существует только в том случае если, его можно наблюдать (является observable). И вот как раз постусловия формулируются в виде предикатов, которые оперируют эти видимым состоянием. Ключевое свойство постусловий — их можно проверить (в принципе, но для этого нет необходимости) и на их основании клиент может сделать определенные выводы. Собственно сам по себе вызов функции необходим только для перевода объекта в состояние, в котором постусловия являются выполненными.
AS>В общем, предлагаю закругляться, никаких новых аргументов уже не появляется.
Ок. обсуждение unique_lock можно считать закрытым. Но у меня еще пара вопросов — считаете ли Вы, что например функция std::string::erase(iterator, iterator) должна проверять итераторы на валидность (и кидать исключение, в случае если дальнейшее выполнение прведет к порче 'состояния')?
Изменится или нет ответ на этот вопрос в таких случаях: 1) существует предусловие говорящее о том что передаваемые итераторы должны быть валидны (в как это формулируется для объекта string) 2) такое предусловие отсутствует.
Если в обоих случаях ответ — да, то Вы приверженец т.н. Defensive Programming Style, я же в свою очередь, нахожусь по другую сторону баррикад — второй случай дла меня неприемлим вообще (почти всегда), для первого — ответ нет. И, разумеется, этот выбор основан не на пустом месте.
Re[4]: api design: return code or exception - formal criteri
Здравствуйте, Andrew S, Вы писали:
AS>На мой взгляд в этом случае именно что ассерты. Пользователь продолжает пользовать невалидный объект — значит ошибка в логике программы, а ошибку в логике надо по возможности отлавливать никак не в релизе. Эксепшены только в случае, если слой более низкого уровня вернул "плохую" ошибку. На плохой хендл, например, многие api так и сделают, так что автоматически без "лишней" рантайм проверки в релизе все будет выглядеть как надо.
Вы не путаете отладку программы с обработкой ошибок?
коды возврата и исключения это инструменты для обработки ошибок, assert макрос для отладки. Конечно их можно использовать не по назначению, но имеет ли смысл ?
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Ищу работу, 3D, SLAM, computer graphics/vision.
Re: api design: return code or exception - formal criteria
К сожалению очень часто програмисты не отличают и не разделяют ошибки исполнения от состояния объектов или результатов выполнения.
пример результата выполнения.
bool fileExist(...);
Если эта функция вернет false это не означает что произошла ошибка и необходимо прервать выполнение программы.
пример ошибки выполнения.
bool fileExist(...);
Во время выполнения сетевой ресурс на который ссылается путь был отключен, это ошибка выполнения fileExist, и вполнен нормально в таком случае кинуть исключение.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[3]: api design: return code or exception - formal criteri
Здравствуйте, minorlogic, Вы писали:
V>> После появились программисты библиотеки, которые поняли, что каждый раз включать галку в студии останавливаться на исключении в отладчике не катит и вставили ассерт перед самим throw. M>Объясните подробнее пожалуйста этот кусок.
Чтобы не перезапускать приложение, которое уже грохнулось на исключении, ставят ассерт перед исключением чтобы вовремя отстановиться в отладчике, обычная практика.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[5]: api design: return code or exception - formal criteri
Здравствуйте, Erop, Вы писали:
V>>Для начало можно сделать коды возврата обязательными параметрами всех нужных функций. E>А что это даст, кроме дальнейшего снижения удобства использования библиотеки? E>IMHO, качественный клинтский код -- это забота его авторов. Библиотека должна позволять писать качественный, и не должна мешать это делать или провоцировать на некачественный. Но бороться?
Одна библиотека не сможет этого обеспечить, нужны ещё нормальные программисты, которые её будут правильно использовать. А на практике, обычно, это не выполняется. Поэтому, иногда, лучше не использовать исключения, которые приводят к болоту, а использовать только коды возврата. При этом пользователь не сможет написать рабочий код без проверки кода возврата, что есть защита от дурака.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[22]: api design: return code or exception - formal criter
Здравствуйте, Николай Ивченков, Вы писали:
НИ>Юрий Жмеренецкий:
ЮЖ>>Нельзя рассматривать исключения как замену assert-у. Кроме того, если быть последовательным, тогда нужно таким же образом проверять все предусловия
НИ>Какое этому обоснование?
Иначе, при таком подходе (очень плохом, имхо), нельзя обнаружить все ошибки (логические).
НИ>>>Предполагается, что программист должен отлавливать исключения std::logic_error (и производные от него) и трактовать их как внутренние ошибки в программе.
ЮЖ>>Для чего? для отлова багов? Единственный возможный вариант при диагностике такой ситуации — вызов abort в месте обнаружения.
НИ>Хорошо, у пользователя программа упала по abort. Разработчику нужно пофиксить багу. Как ты предлагаешь выяснять примерную причину ошибки
Это другой вопрос (решаемый в принципе). Разница между возбуждением исключения и abort в этом случае — нет гарантий, что размотка стека не приведет к инициации других ошибок, нет гарантий что все данные останутся в порядке, нет гарантий что обработчик исключений не содержит багов, и т.д. abort выводит программу из состояния 'баг' с минимальными последствиями. Если же требуется все-таки не падать даже при наличии багов — то все эти проблемы придется все-таки решить.
Но здесь возникает забавное логичское противоречие — если программист может решить эти проблемы не наплодив новых багов — почему он не может их не плодить изначально?
ЮЖ>>Только для полного счастья, повторюсь, необходимо проверять все предусловия и все постусловия. А это приведет к катастрофическому снижению производительности.
НИ>Нужно найти разумный компромисс.
Разумый выход — снижение количества потенциально опасных мест, которые могут привести к остановке (defect -> fault). При большом количестве дефектов (сам по себе дефект не всегда приводит к остановке) появляется проблема с их идентификацией, которую ты озвучил. Вместо строительства подсистемы ловли багов логичнее попытаться снизить общее количество мест их возникновения. Вот здесь как раз можно эксплуатировать ценное свойство пред/пост условий — выводимость из контекста. Не нужно выполнять повторную проверку, там, где ее выполнение выводимо из контекста. И никакой ошибки в этом случае не возникнет. Но для этого требуется две вещи — документация предусловий и следование простому логическому правилу — 'если P то Q'.
ЮЖ>>Если же возбуждать logic_error (вместо вызова abort), то nothrow функции с пред и постусловиями волшебным образом получают возможность возбуждать исключения, соответственно их использование намного усложняется.
НИ>Если функция может выпускать наружу исключения, то она уже не является nothrow.
Я это и имел ввиду.
НИ>Конечно, это может усложнить её использование, но зато взамен мы получаем какую-то информацию об ошибке.
Опять тот же вопрос — т.е. программист обладает достаточной квалификацией, для того чтобы писать код, устойчивый к исключениям в любых местах (+ допускаются неизбежные дополнительные затраты по ресурсам), а проверить предусловия для вызова функции он не в состоянии?
Чуть-чуть усложнили здесь, чуть-чуть усложнили там — увеличивается объем кода, увеличивает число состояний программы — еще больше увеличиваются шансы на ошибку. А ведь это код еще и поддерживать надо... В большинстве случаев — это исскуственное усложнение кода на ровном месте и последующее решение сопутствующих проблем.
PS: Кроме того, всю (а не 'какую-то') необходимую информацию можно получить куда проще — она (вместве с контекстом) доступна перед вызовом.
Re[7]: api design: return code or exception - formal criteri
Здравствуйте, Erop, Вы писали:
V>>Одна библиотека не сможет этого обеспечить, нужны ещё нормальные программисты, которые её будут правильно использовать. А на практике, обычно, это не выполняется. Поэтому, иногда, лучше не использовать исключения, которые приводят к болоту, а использовать только коды возврата. При этом пользователь не сможет написать рабочий код без проверки кода возврата, что есть защита от дурака. E>Во-первых, болото можно легко развести и при исключениях и при кодах возврата. IMHO, при кодах развести проще, так как их проще игнорить. Твой способ "форсировать" проверки элементарно обходится примерно так:
Это не аргумент, так можно сломать любую функцию, коды воврата тут вообще ни причём. E>Это конечно всё ССЗБ, но и asserts писать где попало, это тоже ССЗБ...
Я уже объяснил для чего ассерт.. E>Так что, IMHO, если уж ставится задача форсировать обработку ошибок, то надо таки исключения бросать...
Фраза "форсировать обработку ошибок" уже попахивает говнокодом, вам не кажется?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[5]: api design: return code or exception - formal criteri
Здравствуйте, crable, Вы писали:
V>>>>К чему это всё скажите вы? А к тому что иногда поддержка исключений библиотекой приводит к образованию некоторого пользовательского болота, организованного недобросовестными программистами использующими библиотеку, из которого вам впоследствии будет трудно выбраться. V>>>>Иногда лучше вообще не пользоваться исключениями и "заставлять" пользователя обрабатывать код возврата. C>>>А мне показалось, что лучше не ставить ассерты перед throw и недобросовестеыми тут выглядят как раз программисты библиотеки, сделавшие это. V>>Тогда придётся перезапускать приложение с включёнными галками ловить исключения, что тоже не есть хорошо. C>Во первых, каким образом использование кодов возврата вместо исключений поможет решить проблему с ассертами?
Оно и не должно. Оно решает проблему с конкретными функциями, где кидание исключений не приемлема. C>Во-вторых, в чём же всё-таки были виноваты пользователи билиотеки?
В написании говнокода и непотребщины, которое пришлось переписывать естественно. C>И в третьих, ты сам описал ситуацию, при которой эти ассерты только мешают,
Они не мешали, они стали мешать из-за бросания исключений оттуда, откуда можно было вернуть код возврата. C>более того, я, например, склонен считать, что так будет в большинстве случаев. Добалять ассерты только потому, что кому-то трудно раз в сто лет заставить студию перехватывать исключения выглядит, по меньшей мере нелепо.
Во первых, в достаточно сложном приложении, включать по-умолчанию ловлю ассертов вообще не преемлемо, т.к. ассерты начинают летать в приложении с самого его запуска и вы постоянно будете на них натыкаться в отладчике.
Во-вторых, когда приложение "грохается" на исключении, чтобы воспроизвести падение, надо как минимум привести приложение в то состояние, в котором оно было перед самым падением, а это иногда оччень не просто, из-за количества действий которое нужно сделать и раннее включение ловли исключений этому будет только мешать.
Поэтому, ассерт вставляют перед исключением, чтобы "поймать падение" с первого раза и не мучится с перезапуском приложения и приведением его в состояние перед падением.
C>Кстати, для того, чтобы поставить эту галку вовсе необязятельно перезапускать приложение.
А исключение уже проскочило и не было поймано, поэтому придётся.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[23]: api design: return code or exception - formal criter
Здравствуйте, Юрий Жмеренецкий, Вы писали:
ЮЖ>Иначе, при таком подходе (очень плохом, имхо), нельзя обнаружить все ошибки (логические).
Вообще-то нормальные программы ещё что-то и делают, так что ошибки в них можно найти и по результатам их деятельности
ЮЖ>Это другой вопрос (решаемый в принципе). Разница между возбуждением исключения и abort в этом случае — нет гарантий, что размотка стека не приведет к инициации других ошибок, нет гарантий что все данные останутся в порядке, нет гарантий что обработчик исключений не содержит багов, и т.д. abort выводит программу из состояния 'баг' с минимальными последствиями. Если же требуется все-таки не падать даже при наличии багов — то все эти проблемы придется все-таки решить.
Не правда ваша.
1) обычный assert приводит к тому, что программа делает красиво ручкой, а хотелось бы ещё и документик сохранить, например
2) assert, который провалился у пользователя, ситуация неприятная, но, увы, возможная. Дальше всё упирается в простой вопрос кто за что готов платить Если речь идёт о простом таком пользователе-человеке, то ему хорошо бы получить как-то просто после этого инструкции как решить его проблему. А разработчику хорошо бы как-то получить описание возникшей у пользователя ситуации, приведшей к проявлению ошибки...
3) Собственно между "ошибок много" и "ошибок мало" разница более или менее количественная. Грубо говоря понижение вероятности встретить ошибку в программе стоит денег на разработку и тестирование.
4) Я не знаю ситуаций, когда обработка ошибки совсем не важна. Если уж прога упала, то кто-то что-то должен написать заранее такое, чтобы жизнь продолжилась. Если прога для пользователя, то должно появиться окошко, из которого удобно обратиться в техподдержку, сразу же и инфу об ошибке собрав, если надо, и получив инструкции, если ошибка уже известная...
А если это СУ космического аппарата, то надо что-то по assert тоже что-то делать. Таки чтобы аппарат в таком случае не потерялся, реактор не взорвался и т. д...
ЮЖ>Но здесь возникает забавное логичское противоречие — если программист может решить эти проблемы не наплодив новых багов — почему он не может их не плодить изначально?
Потому, что при разработке системы обработки ошибок, её обычно проектируют и тестируют. ЧТо делает задачу более структурированной, что упрощает жизнь разработчика
ЮЖ>Но для этого требуется две вещи — документация предусловий и следование простому логическому правилу — 'если P то Q'.
Это плохо верифицируемо. Так что это повышает цену разработки и некисло...
Кроме того, во многих случаях пред/пост-условие трудно не то, что бы проверить, но даже хотя бы и выписать формально...
IMHO, разумный компромисс состоит в том, чтобы иметь какую-нибудь вменяемую систему обработки багов программы и ошибок пользователя, и при этом всюду тотально проверять то из пост/пред-условий, что можно проверить легко и быстро.
Самые тормозные проверки можно оставлять только в _DEBUG версии, например...
ЮЖ>Опять тот же вопрос — т.е. программист обладает достаточной квалификацией, для того чтобы писать код, устойчивый к исключениям в любых местах (+ допускаются неизбежные дополнительные затраты по ресурсам), а проверить предусловия для вызова функции он не в состоянии?
Если assert кидается исключениями, и других исключений мы в каком-то коде не ждём, то можно снизить сильно требования к коду. Можно требовать чтобы он корректно разрушал свои данные и всё. НИ транзакционности можно не требовать, ни даже отсутствие утечек ресурсов, если только такие утечки не приводят к каким-то фатальным последствиям.
Всё-таки провал assert'а -- это катастрофа
Просто можно иметь цель сделать так, чтобы катастрофа проходила как в современном авто -- без большого риска потери данных, работы, чего-то ещё, а не как в "копейке", когда всё гарантированно накрывается и все гибнут. И можно "загружать" всё заново...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[25]: api design: return code or exception - formal criter
Здравствуйте, Юрий Жмеренецкий, Вы писали:
ЮЖ>Везде при подобном коде:
ЮЖ>
/// \pre x > 10, p != NULL
ЮЖ>void f(int x, int* p)
ЮЖ>{
ЮЖ> assert(x > 10);
ЮЖ> assert(p);
ЮЖ> if(x > 10)
ЮЖ> throw std::invalid_argument("f::x"); // В клинических случаях добавляется запись в лог, etc.
ЮЖ> // Тип исключения в принципе не так важен, но это по
ЮЖ> // сути логическая ошибка (std::logic_error).
ЮЖ> // Иногда можно увидеть std::runtime_error - но это уже похоже на саботаж
ЮЖ> if(!p)
ЮЖ> throw std::invalid_argument("f::p");
ЮЖ> //...
ЮЖ>}
Кажется я начинаю понимать, у вас другая терминология. Вы пытаетесь проверить DBC входных и т.п. аргументов. Для этих целей лучше написать нормальный обработчик типа CHECK_DC(...); CHECK_PRE_DC(...); CHECK_POST_DC(...); и так далее. Такие обработчики проверяют правильность выполнения программы и вполне резонно могут кидать исключения или настраивать свое поведение в релизной и отладочной сборках. С помощью подобных макросов (или функций) вы сможете легко, без оверхеда обрабатывать входные параметры, определенные состояния объектов и т.п.
Культура же програмирования на С++ подразумевает использования макроса ASSERT (assert) для проверки корректности програмым. Т.е. это проверка которая должна срабатывать ВСЕГДА.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Ищу работу, 3D, SLAM, computer graphics/vision.
api design: return code or exception - formal criteria
Ситуация следующая — разрабатывается небольшой слой кросс-платформенной абстракции (io, sync, mt etc). В который раз возник вопрос, что в каких ситуациях использовать — исключения и коды возврата ошибок.
Есть ли какие-то формальные критерии, по которым можно решить что в данном случае использовать коды возврата ошибки, в данном — исключения.
Например, взять файловые операции. Тут довольно тонкая ситуация — в некоторых случаях важно ошибки обрабатывать прямо рядом с вызовом, в некоторых — не нужно. Хочется иметь какие-то формальные рекомендации, по которым в большинстве случаев можно было бы принимать подобное решение. "Посмотреть как у других" в данном случае не подходит — просто потому, что у других по-разному.
Рассмотрим, например, ситуацию — критическая секция в терминах win32 и именованный мьютекс. С критической секцией вроде без вариантов — там только исключения, тем более, что бросать она их может только в одном редком кейсе, см msdn. Должен ли мьютекс по аналогии бросать эксепшены (читай, инициализироваться в конструкторе), или же иметь методы Create/Open/Close. Как тогда быть с операциями типа wait, которы могут возвращать довольно много различных кодов, часть из которых обычно анализируется сразу рядом, часть — вызывается необратимые последствия...
Если есть ссылки критерии, best practice или что-нибудь подобное — будет супер.
Спасибо!
PS Свое мнение по данному вопросу у меня имеется В данном случае нужны именно формальные критерии, чтобы каждый конкретный случае не вызывал одинаковых вопросов.
AS>>Всем привет.
AS>>Ситуация следующая — разрабатывается небольшой слой кросс-платформенной абстракции (io, sync, mt etc). В который раз возник вопрос, что в каких ситуациях использовать — исключения и коды возврата ошибок.
AS>>Если есть ссылки критерии, best practice или что-нибудь подобное — будет супер.
J>boost.asio
J>все функции в двух экземплярах, одни бросают, другие (у которых есть параметр boost::system::error_code & ec) — не бросают. J>Пример: J>
Ух, сильно. Нужно ли это для каждого метода — вот вопрос. Мое мнение, что для каждого метода это лишнее, и сам же буст этому пример — например, трединг, алгоритмы...
Хочется формальных критериев, когда надо только исключения, когда только результат, когда нужно, например, вот такое раздвонение интерфейса.
Спасибо за мнение, пример наводит на некоторые мысли
AS>>Хочется формальных критериев, когда надо только исключения, когда только результат, когда нужно, например, вот такое раздвонение интерфейса.
J>Имхо, нету таких формальных критериев. J>Все зависит от пользователя: для одного неудача — штатная ситуация, а для другого — фатальная ошибка.
J>Например, должна ли бросать функция открытия файла, если файл не найден?
Вот, вот, примерно это.
J>Если у тебя список файлов, и тебе нужно попробовать их все по порядку и использовать первый нашедшийся, то неудача с одним файлом — это штатный эпизод перебора файлов, а фатальная ошибка — это если вообще ни одного файла нет.
Лучше пример с неименованным мьютексом без секьюрити Там менее очевидно чем с файлом (тем более для файла есть пример — mfc, где как раз сделано более-менее в плане проектирования нормально).
А если с файлом — тогда лучше рассмотреть метод Read или Write. По моим ощущениям, напрмер, в данном случае как раз можно найти некий баланс в виде result code (if success or eof) + exception if failure. Т.е. в 99% юзекейсов покрывается. Так сделано в mfc и, похоже, это довольно неплохой дизайн в таких случаях, хотя раньше я думал иначе . Вот хочется примерно таких рекомендаций, дабы разработчику не надо было думать, что в каких случаях делать, да и все проще, если играть по правилам.
J>А если у тебя всего лишь одно имя файла, то невозможность его открыть — это фатальная ошибка.
С файлами все вообще сложнее — надо делать двухуровневое конструировение, либо делать одноуровневое + порождающую фабрику. Меня больше, повторюсь, интересуют пограничные случаи типа неименованного мьютекса.
J>Так что правильный путь, имхо — это дать возможность пользователю самому решить, что ему нужно, например, как делает boost.asio (можно и по-другому делать).
Но, имхо, не для каждого интерфейса. Либо делать 2 разных интерфейса с возможность move-upgrade одного в другой...
Вот этого бы не хотелось, на самом деле.
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, Andrew S, Вы писали:
AS>>Хочется формальных критериев, когда надо только исключения, когда только результат, когда нужно, например, вот такое раздвонение интерфейса.
J>Имхо, нету таких формальных критериев. J>Все зависит от пользователя: для одного неудача — штатная ситуация, а для другого — фатальная ошибка.
J>Например, должна ли бросать функция открытия файла, если файл не найден? J>Если у тебя список файлов, и тебе нужно попробовать их все по порядку и использовать первый нашедшийся, то неудача с одним файлом — это штатный эпизод перебора файлов, а фатальная ошибка — это если вообще ни одного файла нет. J>А если у тебя всего лишь одно имя файла, то невозможность его открыть — это фатальная ошибка. J>И всё это разнообразие может быть у пользователя в одной функции загрузки конфигов.
J>Так что правильный путь, имхо — это дать возможность пользователю самому решить, что ему нужно, например, как делает boost.asio (можно и по-другому делать).
Думаю что у функции открытия файла должно быть чего-то типа флагов, например create_if_not_exists.
Я считаю так. Если функция не выполнила пост условия, значит это исключение. Определи постусловие у функции и бросай исключение в случае его не достижения.
Правда тут есть тоже тонкость. Например операция удаления файла. Что делать если этого файла и не было. В этом случае вопрос заключается в том, считать ли это ошибкой предусловия или нет. Является ли наличие файла требованием дл функции удаления файла. Мне больше всего нравится решение когда такие проблемы если они есть разруливаются классами стратегий, ну и соответственно есть какая-то стратегия по умолчанию.
Я за то что б бросать исключения и использовать коды воврата только в крайних случаях.
Re[6]: api design: return code or exception - formal criteri
J>>>Так что правильный путь, имхо — это дать возможность пользователю самому решить, что ему нужно, например, как делает boost.asio (можно и по-другому делать).
AS>>Но, имхо, не для каждого интерфейса. Либо делать 2 разных интерфейса с возможность move-upgrade одного в другой... AS>>Вот этого бы не хотелось, на самом деле.
ЮЖ>Если модификация api невозможна, то можно такие варианты рассмотреть: ЮЖ>
ЮЖ>Объект checked_result в операторе присваивания проверяет аргумент на присутствие error_code1 или error_code2, и в случае обнаружения кидает исключение. Либо просто использует что-то вроде 'if(FAILED(hr)) throw ...'.
ЮЖ>Другой вариант — опять nothrow api, + легкие обертки-функторы:
Спасибо за ответ.
Да, про это все тоже думалось. В данном случае надо уменьшить бардак в коде, такие подходы его, на мой взгляд, поощряют — появляется куча условий на довольно высоком уровне. В общем, смысл от исключений тогда почти теряется. В общем, от исключений совсем отказываться не хочется, надо просто регламентировать ситуации...
Здравствуйте, Юрий Жмеренецкий, Вы писали:
ЮЖ>Здравствуйте, Andrew S, Вы писали: ЮЖ>... J>>>Так что правильный путь, имхо — это дать возможность пользователю самому решить, что ему нужно, например, как делает boost.asio (можно и по-другому делать).
AS>>Но, имхо, не для каждого интерфейса. Либо делать 2 разных интерфейса с возможность move-upgrade одного в другой... AS>>Вот этого бы не хотелось, на самом деле.
ЮЖ>Если модификация api невозможна, то можно такие варианты рассмотреть: ЮЖ>
ЮЖ>Объект checked_result в операторе присваивания проверяет аргумент на присутствие error_code1 или error_code2, и в случае обнаружения кидает исключение. Либо просто использует что-то вроде 'if(FAILED(hr)) throw ...'.
А что делать если f1 возвращает значение? делать через out параметры? не всегда удобно/красиво
я не волшебник, я только учусь!
Re[7]: api design: return code or exception - formal criteri
Здравствуйте, IROV.., Вы писали:
IRO>А что делать если f1 возвращает значение? делать через out параметры? не всегда удобно/красиво
Здесь два варианта:
1) Код ошибки при этом возвращается через out параметры (что встречается редко).
2) Ошибкой считается некоторое состояние возвращаемого значения.
В принципе второй вариант очень похож на вариант с возвратом кода ошибки, разница может быть в том, что коды ошибок фиксированы, а "ошибочное" состояние может зависить от контекста/функции. Если разницы нет, то такой вариант эквивалентен первоначальному.
Дополнительно к этому нужно определить минимальный набор данных, которые надо передавать в объект исключения. В первом случае — наверняка это будет непосредственно код ошибки, во втором — с большей долей вероятности _не_ возвращенное значение, а что-то другое, вроде значения GetLastError из WINAPI.
С обработкой на месте эти два варианта могут выглядеть так:
result r;
value v = api::f(.., &r);
if(r == X)
throw exception(r);
//...
value v = api::f(...);
if(v == Y)
throw exception(get_last_error());
Разумеется и такие варианты могут быть актуальными (особенно при единичных вызовах). Вопрос в том, насколько можно их "улучшить" в рамках принятых допущений. Но более концептуальный вопрос — а что считать улучшениями?
Вот например:
// для первого случая
result r;
value v = api::f(.., &r);
ensure(r, r == X);
//...
result r;
value v = api::f(.., &f);
ENSURE(r == X); // макрос для избавления от первого аргумента
// для второго
value v = api::f(...);
ensure(v == Y);
//...
value v = ensure_not_Y(api::f(...));
//...
value v = api::f(...), THROW_IF(v == Y); // макрос
Что здесь лучше/хуже/удобней/красивей?
Re[3]: api design: return code or exception - formal criteri
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Re[9]: api design: return code or exception - formal criteri
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, Юрий Жмеренецкий, Вы писали:
ЮЖ>>Здравствуйте, IROV.., Вы писали:
IRO>>>А что делать если f1 возвращает значение? делать через out параметры? не всегда удобно/красиво
ЮЖ>>Здесь два варианта: ЮЖ>>1) Код ошибки при этом возвращается через out параметры (что встречается редко). ЮЖ>>2) Ошибкой считается некоторое состояние возвращаемого значения. ЮЖ>>Что здесь лучше/хуже/удобней/красивей?
J>Главная проблема с возвращаемыми значениями — это то, что они игнорируются по умолчанию (в отличие от исключений).
Стоп, стоп... мы говорим о другом — об использовании nothrow api.
Я: вот такую конструкцию:
result r = api::f1(...);
if(r == error_code1)
throw exception(...);
r = api::f2(...);
if(r == error_code1)
throw exception(...);
r = api::f3(...);
if(r == error_code1)
throw exception(...);
легко можно преобразовать в такую:
checked_result r(error_code1);
r = api::f1();
r = api::f2();
r = api::f3();
IROV: А что делать если f1 возвращает значение?
Значение здесь — это не то "возращаемое значение" о котором ты говоришь. Это полноценный результат. Во втором варианте, про который я говорил (см. выше) — api::f в общем случае не знает, какое значение клиент воспринимает как исключение. Т.е. функция возвращает строку — а клиента может расценивать пустую(или с некоторым значением) строку как исключительную ситуацию. Здесь для определения ошибка/не ошибка используется внешний по отношению к api::f предикат. В частных случаях, вроде HANDLE из winapi, может существовать предопределенное, "ошибочное" значение (хотя в случае с HANDLE их может несколько).
Собственно и вопрос про лучше/хуже был задан в этом контексте. Например в варианте checked_result как плюсом, так и минусом является сокрытие предиката проверки.
J>Поэтому если код написан безопасно по отношению к исключениям
Это минимальное требование, без которого не стоит связываться с исключениями (имхо).
J>то у нас есть автоматическая гарантия, что ни одна ошибка не будет пропущена — против лома нет приема, если исключение вылетело, то оно будет лететь, пока его не поймаешь явно. С возвращаемыми же значениями в варианте два пропустить ошибку легче легкого, ее ведь даже не видно...
Это уже проблема api — возврат кода ошибки через out параметр...
J>В этом смысле первый вариант мне кажется лучшим — по крайней мере, ты говоишь явно, что собираешься обрабатывать ошибки без исключений: ты объявляешь переменную с кодом ошибки и потом пихаешь ее во все вызовы, из которых ты не хочешь получить исключение.
Ты про какой вариант говоршь? Я предлагал наоборот (поскольку api не кидает исключений) — указанные коды трансформируются в исключения. В вариатнах с ensure — copy-paste ошибка, постусловием должнен быть 'assert(exp)', т.е. 'ensure(v != Y)'. Собственно семантика такой функции/макроса похожа на assert, но она во-первых всегда присутствует в коде, во-вторых кидает исключение. Возможная проблема здесь — передача дополнительных аргументов.
J>Конечно, тут тоже легко пропустить обработать ошибку, особенно при копи-пейсте, но это уже не будет настолько невидимым, как в варианте два — по крайней мере, код ошибки присутствует во всех вызовах явно, и то, что его не обработали — видно.
Уточни, пожалуйста, про какой вариант идет речь, а то я начинаю запутываться.
Re[2]: api design: return code or exception - formal criteri
M>Совсем формальным критерием может служить ответ на вопрос M>"возможно ли дальнейшее выполнение если ..."
Вот, наконец то
Именно про это я и говорил. Примерно то же самое у меня и стоит первым в списке формальных критериев. Но этого мало, в смысле формализма — получается слишком много степеней свободы. Я попытался ограничить, он все равное есть ощущение, что можно и получше...
Спасибо.
M>Например
M>1. в конструкторе обертки над мютексом, мы не смогли создать именованный мютекс. В этом случае лучше кинуть ексепшен, потому что объект повиснет в невалидном состоянии. Если этого не делать , то пользователь класса может и забыть проверить валидное создание объекта.
M>2. Объект поток не смог открыться и вернул напримр false в методе stream.open(), после этого любая попытка прочесть из него или записать должны кидать эксепшены.
M>Ну и опять же все зависит от сценария использования.
Здравствуйте, Andrew S, Вы писали:
AS>Всем привет.
AS>Ситуация следующая — разрабатывается небольшой слой кросс-платформенной абстракции (io, sync, mt etc). В который раз возник вопрос, что в каких ситуациях использовать — исключения и коды возврата ошибок.
Есть еще вариант с передачей в эту библиотеку параметра — ссылки на пользовательскую функцию обработки ошибок, и пусть пользователь решает, что в этом обработчике написать
У самого аналогичная неясность...
Re[2]: api design: return code or exception - formal criteri
AS>>PS Свое мнение по данному вопросу у меня имеется В данном случае нужны именно формальные критерии, чтобы каждый конкретный случае не вызывал одинаковых вопросов.
J>О, я придумал формальный критерий: всегда должны использоваться исключения, потому что они обеспечивают гарантию непродолжения в случае ошибки, не давая програмисту неявно ошибиться.
Удобство использования страдает. Пример — тот же File.
J>Отказ от исключений и работа через коды ошибок (в любом виде, будь то возвращаемые значения или out-параметры, как в Boost.Asio) — это ручная оптимизация, которая должна применяться адекватно, как и любая другая оптимизация, т.е. после профилирования и т.п. J>Это на уровне приложения.
J>На уровне библиотеки: поскольку библиотеке неведомо, как она будет применяться, то она должна предоставить оба интерфейса — один для 99% процентов случаев, давая наиболее чистый код пользователя, а другой — для тех мест, где надо все заоптимизировать (иначе пользователю будет неудобно — ему в этих местах придется пользоваться другой библиотекой).
В результате разработчик будет делать как ему удобнее — не проверять коды. Плюсом придется писать кучу апгрейдов из одного интерфейса в другой...
По-моему, вопрос изначально нужно поставить так: нужна ли раскрутка стека для обработки исключительной ситуации? Если мы предполагаем, что в ряде случаев она нужна (полезна), то следует предоставить функцию, генерирующую исключение. Если мы предполагаем, что в ряде случаев исключительную ситуацию нужно обрабатывать на месте и при этом использование кодов исключительных ситуаций возможно и получается заметно удобнее/эффективнее, чем использование try/catch, то следует предоставить функцию, сообщающую код исключительной ситуации. Соответственно, если подходят оба случая, то следует предоставить оба варианта (выше было показано, как это можно сделать). Отмечу, что это применимо к исключительным ситуациям вообще, а не только к ошибкам. Например, то, что пользователь захотел отменить какое-то длительное вычисление, ошибкой не является, но является исключительной ситуацией, и раскрутку стека при этом удобно выполнить через генерацию исключения (как видно, именно удобство механизма исключений даёт повод к его применению).
Re[3]: api design: return code or exception - formal criteri
Здравствуйте, Andrew S, Вы писали:
J>>О, я придумал формальный критерий: всегда должны использоваться исключения, потому что они обеспечивают гарантию непродолжения в случае ошибки, не давая програмисту неявно ошибиться.
AS>Удобство использования страдает. Пример — тот же File.
Да ладно, где оно там так уж фатально страдает?
В примере с перебором файла разницы практически никакой.
AS>В результате разработчик будет делать как ему удобнее — не проверять коды. Плюсом придется писать кучу апгрейдов из одного интерфейса в другой...
Имхо, удобнее как раз с исключениями — ничего явно не надо проверять и бояться, что что-то забыл проверить и пытаешься записать в неоткрытый файл (например).
Еще раз, идея следующая — коды ошибок должны рассмативаться как оптимизация (да, и еще один случай забыл — когда пишем небросающий код для обеспечения безопасности исключений).
Здравствуйте, minorlogic, Вы писали:
M>1. в конструкторе обертки над мютексом, мы не смогли создать именованный мютекс. В этом случае лучше кинуть ексепшен, потому что объект повиснет в невалидном состоянии. Если этого не делать , то пользователь класса может и забыть проверить валидное создание объекта.
Согласен.
И вообще, временный объект любого типа может быть участником выражения, а не отдельного стейтмента.
Прервать вычисление выражения можно только через исключение.
Либо срезать углы, тщательно проверяя операнды (подобно тому, как в плавающей арифметике поступают с NaN).
M>2. Объект поток не смог открыться и вернул напримр false в методе stream.open(), после этого любая попытка прочесть из него или записать должны кидать эксепшены.
А вот здесь можно и ассерты, ибо если пользователь не проверил возвращаемое значение — он ССЗБ.
Вообще, у потоков есть флаг ошибки, срезающий углы.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re: api design: return code or exception - formal criteria
Здравствуйте, Andrew S, Вы писали:
AS>Если есть ссылки критерии, best practice или что-нибудь подобное — будет супер.
Скотт Майерс, "Эффективное использование C++: 35 новых рекомендаций", глава 3, в особенности Правило 15 "Оценивайте затраты на обработку исключений"
Почитал-почитал, и понял, почему разработчики известных мне библиотек так осторожно относятся к использованию исключений.
P.S. Хотя, возможно, эта книга устарела? Написана 13 лет назад, а, как мне снисходительно пояснил на собеседовании один "молодой, да ранний" гуру вкуривший непосредственно стандарт, "С тех пор C++ радикально изменился".
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Re[3]: api design: return code or exception - formal criteri
M>>2. Объект поток не смог открыться и вернул напримр false в методе stream.open(), после этого любая попытка прочесть из него или записать должны кидать эксепшены.
К>А вот здесь можно и ассерты, ибо если пользователь не проверил возвращаемое значение — он ССЗБ.
На мой взгляд в этом случае именно что ассерты. Пользователь продолжает пользовать невалидный объект — значит ошибка в логике программы, а ошибку в логике надо по возможности отлавливать никак не в релизе. Эксепшены только в случае, если слой более низкого уровня вернул "плохую" ошибку. На плохой хендл, например, многие api так и сделают, так что автоматически без "лишней" рантайм проверки в релизе все будет выглядеть как надо.
Но, тут появляется неожиданная тонкость. Возьмем, к примеру, unique_lock а-ля буст:
Правомерно ли тут использование именно исключения, а не ассерта? На самом деле, вопрос интересный. С одной стороны, ситуация похожа на то, что мы видели ранее со стримом. С другой стороны, ситуация _кардинально_ отличается. В случае со стримом кейс, где идет работа далее с невалидным объектом — основной сценарий использования. Тут же — дополнительный (например, из-за ошибки пытаемся залочить рекурсивно). Разница в том, что в одном случае это отлавливается сразу же, и не приводит к фатальному изменению состояния объекта (ну или не должна приводить). Тут же — приводит, очевидным образом, сделав 2 раза лок (первый раз неявно, в конструкторе), сделать 2 раза анлок мы уже не сможем, по вполне очевидным причинам
Стоит ли тут использовать исключения или assert? Я, например, не знаю. Мнения?
В общем, вот все это и хочется формализовать
Более того, надо еще учесть и требования к перфомансу. Но это вторично. Главное — не дать проигнорировать фатальную ошибку, ну и не писАть лишнего — как проверок возвращаемых кодов, так и лишних try-catch. И то, и то, на мой взгляд, вполне сравнимое зло.
J>>>О, я придумал формальный критерий: всегда должны использоваться исключения, потому что они обеспечивают гарантию непродолжения в случае ошибки, не давая програмисту неявно ошибиться.
AS>>Удобство использования страдает. Пример — тот же File. J>Да ладно, где оно там так уж фатально страдает?
Например, проходимся по списку файлов, пробуем открыть. Если нет — ничего страшного, пробуем следующий. На мой взгляд, именно для таких кейсов в файла помимо конструктора должна быть еще и open. Кроме того, open может возвращать расширенный статус — например, успешность + то, что файл уже существовал. Это позволяет во многих случаях _значительно_ упростить логику за счет атомарности такой проверки. Из того же конструктора такой функциональности нормальным способом не добиться.
J>В примере с перебором файла разницы практически никакой.
AS>>В результате разработчик будет делать как ему удобнее — не проверять коды. Плюсом придется писать кучу апгрейдов из одного интерфейса в другой...
J>Имхо, удобнее как раз с исключениями — ничего явно не надо проверять и бояться, что что-то забыл проверить и пытаешься записать в неоткрытый файл (например).
J>Еще раз, идея следующая — коды ошибок должны рассмативаться как оптимизация (да, и еще один случай забыл — когда пишем небросающий код для обеспечения безопасности исключений).
Здравствуйте, Andrew S, Вы писали:
J>>>>О, я придумал формальный критерий: всегда должны использоваться исключения, потому что они обеспечивают гарантию непродолжения в случае ошибки, не давая програмисту неявно ошибиться.
AS>>>Удобство использования страдает. Пример — тот же File. J>>Да ладно, где оно там так уж фатально страдает?
AS>Например, проходимся по списку файлов, пробуем открыть. Если нет — ничего страшного, пробуем следующий.
А что будет дальше, после успешного открытия? Врядли сохранение объекта file в массиве... Чтение/запись, какая-то обработка — все эти операции также могут кидать исключения. Даже если решить "проблему" c open, вероятность получить в итоге подобный код:
имеется. Собственно вопрос — почему бы сразу не использовать кидающий конструктор для открытия в функции process_file (или во вложенной функции, в случае если process_file только обрабатывает исключения)? Или предполагается переписать process_file на версию, в которой используются некидающие варианты всех вызываемых функций (для избавления от try/catch)?
Re[4]: api design: return code or exception - formal criteri
AS>Правомерно ли тут использование именно исключения, а не ассерта? На самом деле, вопрос интересный. С одной стороны, ситуация похожа на то, что мы видели ранее со стримом. С другой стороны, ситуация _кардинально_ отличается. В случае со стримом кейс, где идет работа далее с невалидным объектом — основной сценарий использования. Тут же — дополнительный (например, из-за ошибки пытаемся залочить рекурсивно). Разница в том, что в одном случае это отлавливается сразу же, и не приводит к фатальному изменению состояния объекта (ну или не должна приводить). Тут же — приводит, очевидным образом, сделав 2 раза лок (первый раз неявно, в конструкторе), сделать 2 раза анлок мы уже не сможем, по вполне очевидным причинам AS>Стоит ли тут использовать исключения или assert? Я, например, не знаю. Мнения?
Конкретно здесь, если рассматривать только unique_lock — assert, т.к. очевидно нарушение предусловия: owns_lock() == false. + есть метод для проверки(owns_lock), + еще некоторые аргументы. Но Lockable Concept (к которой относится lock) не содежит функции owns_lock, поэтому предусловия невозможно обобщить в общем случае. Скорее всего так сделано для дальнейшего расширения набора lock types.
Lockable Concept
The Lockable concept models exclusive ownership. A type that implements the Lockable concept shall provide the following member functions:
void lock();
bool try_lock();
void unlock();
void lock()
Effects:
The current thread blocks until ownership can be obtained for the current thread.
Postcondition:
The current thread owns *this.
Throws:
boost::thread_resource_error if an error occurs.
При такой формулировке (общей для всех lock types), с отсутствующими предусловиями — возбуждение исключения единственно верный вариант.
Re[2]: api design: return code or exception - formal criteri
Здравствуйте, slava_phirsov, Вы писали:
_>Почитал-почитал, и понял, почему разработчики известных мне библиотек так осторожно относятся к использованию исключений.
_>P.S. Хотя, возможно, эта книга устарела? Написана 13 лет назад, а, как мне снисходительно пояснил на собеседовании один "молодой, да ранний" гуру вкуривший непосредственно стандарт, "С тех пор C++ радикально изменился".
ИМХО книга отнюдь не устарела.
C++ конечно изменился, но исключения были и тогда, а реализация стандартом не определяется.
Что изменилось, так это аппаратное обеспечение — компы стали пошустрее.
Но всё равно в гэйм деве зачастую исключения отключают нафиг — производительность страдает.
Re[4]: api design: return code or exception - formal criteri
J>>>>>О, я придумал формальный критерий: всегда должны использоваться исключения, потому что они обеспечивают гарантию непродолжения в случае ошибки, не давая програмисту неявно ошибиться.
AS>>>>Удобство использования страдает. Пример — тот же File. J>>>Да ладно, где оно там так уж фатально страдает?
AS>>Например, проходимся по списку файлов, пробуем открыть. Если нет — ничего страшного, пробуем следующий.
ЮЖ>А что будет дальше, после успешного открытия? Врядли сохранение объекта file в массиве... Чтение/запись, какая-то обработка — все эти операции также могут кидать исключения. Даже если решить "проблему" c open, вероятность получить в итоге подобный код:
После этого может быть все что угодно. Например, инициация асинхронной операции, где, как вы понимаете, исключений быть не может.
В общем, не стОит упрощать — File слишком сложный объект, чтобы так просто описать все его юзе кейсы.
ЮЖ>>Effects:
ЮЖ>>The current thread blocks until ownership can be obtained for the current thread.
ЮЖ>>Postcondition:
ЮЖ>>The current thread owns *this.
ЮЖ>>Throws:
ЮЖ>>boost::thread_resource_error if an error occurs.
ЮЖ>>При такой формулировке (общей для всех lock types), с отсутствующими предусловиями — возбуждение исключения единственно верный вариант.
AS>Не согласен. Приведенная формулировка концепта никоим образом не влияет на то, что именно должен делать lock холдер в случае нарушения _своего_ предусловия.
Очень даже влияет — объект, моделирующий этот концепт, не должен усиливать предусловия (и ослаблять постусловия) для функции lock. unique_lock не имеет "своего" предусловия для ф-ии lock, т.к:
Specializations of boost::unique_lock model the TimedLockable concept if the supplied Lockable type itself models TimedLockable concept (e.g. boost::unique_lock<boost::timed_mutex>), or the Lockable concept otherwise.
Re[5]: api design: return code or exception - formal criteri
Здравствуйте, Quasi, Вы писали:
Q>void f(boost::system::error_code& ec = boost::system::throws);
Q>Нельзя невольно проигнорировать ошибку, исключает необходимость дублирования интерфейса
И исключает возможность привязки по указателю на функцию
Юрий Жмеренецкий:
ЮЖ>Throws: ЮЖ>boost::thread_resource_error if an error occurs. [/q]
ЮЖ>При такой формулировке (общей для всех lock types), с отсутствующими предусловиями — возбуждение исключения единственно верный вариант.
Какой же он верный, когда boost::lock_error и boost::thread_resource_error — это два несвязанных наследованием исключения? IMHO, тут в любом случае несоответствие концепции получается. Здесь, вероятно, нужна более общая концепция — что-то вроде OnceLockable. Объект, соответствующий концепции Lockable (MultiLockable), мог бы быть использован как OnceLockable, но не наоборот.
Re[6]: api design: return code or exception - formal criteri
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, Quasi, Вы писали:
Q>>void f(boost::system::error_code& ec = boost::system::throws);
Q>>Нельзя невольно проигнорировать ошибку, исключает необходимость дублирования интерфейса J>И исключает возможность привязки по указателю на функцию
Здравствуйте, Николай Ивченков, Вы писали:
НИ>Юрий Жмеренецкий:
ЮЖ>>Throws: ЮЖ>>boost::thread_resource_error if an error occurs. [/q]
ЮЖ>>При такой формулировке (общей для всех lock types), с отсутствующими предусловиями — возбуждение исключения единственно верный вариант.
НИ>Какой же он верный, когда boost::lock_error и boost::thread_resource_error — это два несвязанных наследованием исключения?
Вопрос Андрея был в том, что использовать — исключение или ассерт. Согласно формулировкам приведенным в документции ассерты использовать нельзя, но на использование исключения ни он, ни ты, как я понял, не согласны. Как в этом случае можно рассуждать о конкретном типе исключения? Это уже следующая 'итерация' вопроса — реализация выбранного способа (и я не утверждаю что это хороший способ — просто документация не оставляет другого выбора).
Re[7]: api design: return code or exception - formal criteri
Здравствуйте, Quasi, Вы писали:
Q>Здравствуйте, jazzer, Вы писали:
J>>Здравствуйте, Quasi, Вы писали:
Q>>>void f(boost::system::error_code& ec = boost::system::throws);
Q>>>Нельзя невольно проигнорировать ошибку, исключает необходимость дублирования интерфейса J>>И исключает возможность привязки по указателю на функцию
Q>
Q>В чем собственно проблема? Или я неправильно понял?
1) нельзя к указателю на функцию привязать
2) нужно все время писать boost::ref(boost::system::throws)
Когда просто вызваешь функцию, разницы никакой, хоть они одна, хоть две, а вот связать такую функцию с указателем уже нельзя.
И даже функцию-переходник без параметров нельзя сделать — будет ругаться на неоднозначность.
Так что для пользователей твоей библиотеки никаких плюсов нету, им гораздо удобнее иметь две функции, котороые обе можно связывать с указателями.
Здравствуйте, Quasi, Вы писали:
Q>>>В чем собственно проблема? Или я неправильно понял? J>>1) нельзя к указателю на функцию привязать Q>А интересно, а откуда такая необходимость, с учетом того, что сигнатура функции содержит С++ типы
а какая разница, какие типы содержит сигнатура?
J>>2) нужно все время писать boost::ref(boost::system::throws) Q>Тут тоже интересный момент, в ином случае, когда их две : Q>
вопрос был собственно в том, зачем вообще в C++ проекте использовать указатели на функции С++ библиотеки, использование функторов, например, boost(почти std)::function дает намного больше гибкости, а в этом случае биндить перегруженные функции менее удобно. Единственное, что приходит в голову, это оптимизация
Re[7]: api design: return code or exception - formal criteri
НИ>>Юрий Жмеренецкий:
ЮЖ>>>Throws: ЮЖ>>>boost::thread_resource_error if an error occurs. [/q]
ЮЖ>>>При такой формулировке (общей для всех lock types), с отсутствующими предусловиями — возбуждение исключения единственно верный вариант.
НИ>>Какой же он верный, когда boost::lock_error и boost::thread_resource_error — это два несвязанных наследованием исключения?
ЮЖ>Вопрос Андрея был в том, что использовать — исключение или ассерт. Согласно формулировкам приведенным в документции ассерты использовать нельзя, но на использование исключения ни он, ни ты, как я понял, не согласны. Как в этом случае можно рассуждать о конкретном типе исключения? Это уже следующая 'итерация' вопроса — реализация выбранного способа (и я не утверждаю что это хороший способ — просто документация не оставляет другого выбора).
Я не против использования исключений. Мое мнение — тут _другая_ мотивация, а не то, что вы пытаетесь рассказывать
Locable, например, кидает исключения и в случае lock_guard, который в свою очередь, никаких своих исключений кидать не может. Но. Его состояние при этом остается валидным, в отличие от unique_lock. Все остальное — вторичные следствия, а никак не первичные.
AS>>Не согласен. Приведенная формулировка концепта никоим образом не влияет на то, что именно должен делать lock холдер в случае нарушения _своего_ предусловия.
ЮЖ>Очень даже влияет — объект, моделирующий этот концепт, не должен усиливать предусловия (и ослаблять постусловия) для функции lock. unique_lock не имеет "своего" предусловия для ф-ии lock, т.к:
ЮЖ>
Specializations of boost::unique_lock model the TimedLockable concept if the supplied Lockable type itself models TimedLockable concept (e.g. boost::unique_lock<boost::timed_mutex>), or the Lockable concept otherwise.
И что? unique_lock никоим образом не ослабляет концепт Locable ни при ассерте, ни при генерации исключения. Просто при генерации исключения lockable'ом он остается в невалидном состоянии. А насчет дополнительного предусловия — как раз метод lock его имеет. У unique_lock есть внутреннее состояние, смотрите реализацию.
Здравствуйте, Andrew S, Вы писали:
AS>>>Не согласен. Приведенная формулировка концепта никоим образом не влияет на то, что именно должен делать lock холдер в случае нарушения _своего_ предусловия.
ЮЖ>>Очень даже влияет — объект, моделирующий этот концепт, не должен усиливать предусловия (и ослаблять постусловия) для функции lock. unique_lock не имеет "своего" предусловия для ф-ии lock, т.к:
ЮЖ>>
Specializations of boost::unique_lock model the TimedLockable concept if the supplied Lockable type itself models TimedLockable concept (e.g. boost::unique_lock<boost::timed_mutex>), or the Lockable concept otherwise.
AS>И что? unique_lock никоим образом не ослабляет концепт Locable
Что такое "ослабление концепта"?
AS> ни при ассерте, ни при генерации исключения.
В случае использования Вашей формулировки — наличия "_своего_ предусловия" возникает очевидное нарушение LSP, аналогичное нарушению здесь:
struct A
{
/// \pre a > 0virtual void f(int a) = 0;
};
struct B : A
{
/// \pre a > 100 void f(int a);
};
Концептуально, 'A' эквивалентно Lockable, а B — unique_lock. Эта эквивалентность следует из документации (которая сама по себе может содержать ошибки, быть неактуальной и т.п.)
Имея на руках любой объект, моделирующий Lockable, для вызова метода lock мы должны выполнить только предусловия этого метода (отсутствие сформулированных предусловий означает что метод можно вызвать в любое время, в любом состоянии объекта, и т.д. — это общепринятая практика). Проблема в том, что unique_lock, согласно документации, должен выполнять требования, налагаемые на все методы Lockable. И документация на unique_lock::lock отсутствует (среди других функций) именно по этой причине.
Как только 'принадлежность' к Locable будет снята, то все ваши выкладки (и мои в т.ч., в первом сообщении я упоминал об этом) становятся правдоподобными.
AS>Просто при генерации исключения lockable'ом он остается в невалидном состоянии.
Валидное/невалидное — здесь это субъективное определение, образующей здесь служат пред и постусловия.
AS>А насчет дополнительного предусловия — как раз метод lock его имеет.
Определение контракта по реализации — это не очень хороший метод, т.к. во первых — мы берем за эталон контракт 'такой как есть', вместо того, который 'должен быть', во вторых — это совершенно лишняя работа, в третьих — реализация может быть недоступна.
AS>У unique_lock есть внутреннее состояние, смотрите реализацию.
Наличие/отсутствие состояния — это следствие, вызванное необходимостью выполнения контрактов.
Re[9]: api design: return code or exception - formal criteri
AS>>И что? unique_lock никоим образом не ослабляет концепт Locable
ЮЖ>Что такое "ослабление концепта"?
Увеличивает множество возможных состояний, например, при помощи адаптера.
AS>> ни при ассерте, ни при генерации исключения.
ЮЖ>В случае использования Вашей формулировки — наличия "_своего_ предусловия" возникает очевидное нарушение LSP, аналогичное нарушению здесь:
ЮЖ>
struct A
ЮЖ>{
ЮЖ> /// \pre a > 0
ЮЖ> virtual void f(int a) = 0;
ЮЖ>};
ЮЖ>struct B : A
ЮЖ>{
ЮЖ> /// \pre a > 100
ЮЖ> void f(int a);
ЮЖ>};
ЮЖ>Концептуально, 'A' эквивалентно Lockable, а B — unique_lock. Эта эквивалентность следует из документации (которая сама по себе может содержать ошибки, быть неактуальной и т.п.)
Не эквивалентно. unque_lock и locakble — абсолютно разные сущности, они всего-лишь сотрудничают, поэтому (теоретически) unique_lock может реализовать _любой_ контракт на свой lock. Как — это ее проблемы, с точки зрения пользователя это совершенно другая сущность, никак не locable. B и A же связаны гораздо сильнее, а именно отношение наследования и полиморфного поведения.
ЮЖ>Имея на руках любой объект, моделирующий Lockable, для вызова метода lock мы должны выполнить только предусловия этого метода (отсутствие сформулированных предусловий означает что метод можно вызвать в любое время, в любом состоянии объекта, и т.д. — это общепринятая практика). Проблема в том, что unique_lock, согласно документации, должен выполнять требования, налагаемые на все методы Lockable. И документация на unique_lock::lock отсутствует (среди других функций) именно по этой причине.
lock у unique_lock при корректном поведении пользователя в любом случае контракту lockable будет удовлетворять — ведь нигде не сказано, например, что нельзя два раза подряд вызвать lock у lockable. Сказано лишь, вызову lock должен соответствовать unlock (иначе, очевидно, не было бы возможно рекурсивных lockable — recursive_mutex). Ввиду наличия контракта на свое состояние (что unique_lock может вызывать lock только один раз ввиду семантики самого unique_lock, а не lockable), у unique_lock после дополнительного вызова состояние оказывается неверным — он не может обеспечить контракт lockable.
Здравствуйте, Andrew S, Вы писали:
ЮЖ>>В случае использования Вашей формулировки — наличия "_своего_ предусловия" возникает очевидное нарушение LSP, аналогичное нарушению здесь:
ЮЖ>>
struct A
ЮЖ>>{
ЮЖ>> /// \pre a > 0
ЮЖ>> virtual void f(int a) = 0;
ЮЖ>>};
ЮЖ>>struct B : A
ЮЖ>>{
ЮЖ>> /// \pre a > 100
ЮЖ>> void f(int a);
ЮЖ>>};
ЮЖ>>Концептуально, 'A' эквивалентно Lockable, а B — unique_lock. Эта эквивалентность следует из документации (которая сама по себе может содержать ошибки, быть неактуальной и т.п.)
AS>Не эквивалентно.
Собственно вот он — камень преткновения.
AS>unque_lock и locakble — абсолютно разные сущности, они всего-лишь сотрудничают AS>поэтому (теоретически) unique_lock может реализовать _любой_ контракт на свой lock. Как — это ее проблемы, с точки зрения пользователя это совершенно другая сущность, никак не locable. B и A же связаны гораздо сильнее, а именно отношение наследования и полиморфного поведения.
В оригинальной формулировке LSP дихотомия subtype/supertype не имеет никакого отношения к классическому наследованию. Наследование — один из возможных способов выражения таких отношений.
С этой точки зрения — утверждение "unque_lock и locakble — абсолютно разные сущности" неверно.
Между ними существуют отношения subtype/supertype, несмотря на то что это декларируется только документацией. Соответственно и поведение unque_lock (как lockable объекта) должно быть таким, как того требует спецификация lockable concept. Некоторые девиации все же допустимы — реализация может изменять контракты, но только в жестко ограниченных рамках — нельзя усиливать предусловия и ослаблять постусловия, Так же к provable property можно отнести и генерацию исключений методами — см. замечание Николая про boost::thread_resource_error.
Все это нужно для того, что бы в контексте, в котором выполняются предусловия для Lockable:lock можно было свободно использовать любые методы, в качестве параметров которых используется сущность реализующая Lockable concept, например:
Preconditions:
The value_type of ForwardIterator must implement the Lockable concept
ЮЖ>>Имея на руках любой объект, моделирующий Lockable, для вызова метода lock мы должны выполнить только предусловия этого метода (отсутствие сформулированных предусловий означает что метод можно вызвать в любое время, в любом состоянии объекта, и т.д. — это общепринятая практика). Проблема в том, что unique_lock, согласно документации, должен выполнять требования, налагаемые на все методы Lockable. И документация на unique_lock::lock отсутствует (среди других функций) именно по этой причине.
AS>lock у unique_lock при корректном поведении пользователя в любом случае контракту lockable будет удовлетворять
Не в любом. Как только вы вводите "контракт на свое состояние" _и_ этот контракт не вписывается (см. выше про ограничения) в контракт для supertype — возникает нарушение LSP. В этом случае нельзя использовать unique_lock там где требуется любая сущность, реализующая Lockable, т.к. она обладает уже другим контрактом.
Очевидный выход — либо устранить какую-либо связь между unique_lock и Lockable, либо изменить контракты Lockable. В той ситуации, которую мы имеем — добавление любого предусловия (отличного от true) для unique_lock::lock делает реализацию unique_lock некорректной (для существующей спецификации).
Re[2]: api design: return code or exception - formal criteri
Здравствуйте, Vain, Вы писали:
V>Иногда лучше вообще не пользоваться исключениями и "заставлять" пользователя обрабатывать код возврата.
IMHO, пользователей библиотеки, которые "развели болото" не удастся заставить верно обрабатывать коды возврата...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[7]: api design: return code or exception - formal criteri
Юрий Жмеренецкий:
ЮЖ>Вопрос Андрея был в том, что использовать — исключение или ассерт. Согласно формулировкам приведенным в документции ассерты использовать нельзя, но на использование исключения ни он, ни ты, как я понял, не согласны. Как в этом случае можно рассуждать о конкретном типе исключения? Это уже следующая 'итерация' вопроса — реализация выбранного способа (и я не утверждаю что это хороший способ — просто документация не оставляет другого выбора).
У меня есть подозрения, что тут всё же подразумеваются некие "слабые" концепции, выражающие общие требования к классу без предусловий выполнения этих требований. Т.е. класс удовлетворяет требованиям "слабой" концепции, если существует специфичный для него набор предусловий, при выполнении которых он поддерживает все операции в соответствии с указанной семантикой. В черновике нового стандарта, насколько я вижу, описывать блокировки через концепции вообще не планировали. Там таких непоняток уже не возникает. Например, в отношении нерекурсивного мьютекса std::mutex чётко сказано, что попытка повторного вызова lock в пределах одного потока влечёт undefined behavior — фактически здесь предусловием вызова lock является отсутствие блокировки мьютекса текущим потоком. Вероятно, то же самое следует подразумевать и в отношении boost::mutex, однако концепция Lockable таких ограничений не содержит.
Re[8]: api design: return code or exception - formal criteri
[...]
НИ>У меня есть подозрения, что тут всё же подразумеваются некие "слабые" концепции, выражающие общие требования к классу без предусловий выполнения этих требований. Т.е. класс удовлетворяет требованиям "слабой" концепции, если существует специфичный для него набор предусловий, при выполнении которых он поддерживает все операции в соответствии с указанной семантикой.
Для каких целей нужны такие концепции? Да, они описывают поведение, но только в некотором классе состояний [объекта], причем не специфицируют метод определения принадлежности к этому классу. Как c их использованием можно сделать, например, следующее:
Даже если ввести метод — предикат (P), который отвечает на вопрос 'удовлетворяет ли объект класса X концепции Y в настоящий момент?' то появляются дополнительные проблемы: например — любой вызов метода, относящегося к концепции Y может (если этому не препятствовать) привести к тому, что значение P станет равным false, т.е. фактически вызов метода концепции выводит объект из состояния, в котором он удовлетворял этой концепции.
Поведение unique_lock как раз попадает под этот сценарий (схематично) — в 'unlocked' состоянии: частичное соответствие Lockable concept (возможность вызова 'lock'), после вызова lock — опять частичное соответствие (возможность вызова 'unlock'). Плюс, в любом состоянии статическое соответствие (набор функций).
Т.е. фактически получается конечный автомат из концепций, + функции для перехода между ними, + (опционально) предикаты для проверок/наблюдения.
После спецификации всего этого можно описывать конкретные сущности, определяя структуру такого конечного автомата (для поддержки рекурсии КА недостаточно, но это уже детали), а так же пред и постусловия для переходов между состояниями ('микро' концепциями). Но в таком случае уже нельзя просто так сказать 'объект класса X моделирует/реализует Lockable concept'.
В той же ситуации, которую мы имеем на текущий момент, повторюсь, введение любого предусловия для метода unique_lock::lock приводит к нарушению LSP.
Re[11]: api design: return code or exception - formal criter
ЮЖ>>>Концептуально, 'A' эквивалентно Lockable, а B — unique_lock. Эта эквивалентность следует из документации (которая сама по себе может содержать ошибки, быть неактуальной и т.п.)
AS>>Не эквивалентно. ЮЖ>Собственно вот он — камень преткновения.
AS>>unque_lock и locakble — абсолютно разные сущности, они всего-лишь сотрудничают AS>>поэтому (теоретически) unique_lock может реализовать _любой_ контракт на свой lock. Как — это ее проблемы, с точки зрения пользователя это совершенно другая сущность, никак не locable. B и A же связаны гораздо сильнее, а именно отношение наследования и полиморфного поведения.
ЮЖ>В оригинальной формулировке LSP дихотомия subtype/supertype не имеет никакого отношения к классическому наследованию. Наследование — один из возможных способов выражения таких отношений.
Кто сказал, что unique_lock и Locable связаны отношением подстановки аналогично приведенному вами А и Б? Для пользователя эти сущности имеют разный контракт. Хотя контракт unique_lock и включает в себя _часть_ контракта Lockable, он имеет дополнительные ограничения и самое главное — различные протоколы. Этак можно сказать, что любые объекты, которые имеют конструктор и деструктор, являются подтипами.
Итого. Никакого отношения к связи между unique_lock и lockable принцип LSP и наследование, как его реализация, не имеет.
Например, вот:
Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).
Для unique_lock, очевидно, это не выполняется. У этих сущностей разные контракты, и пример этой разницы хотя бы для рекурсивного Locable я уже привел. Но, разницы не сколько и не столько в этом — а в том, что Loсkable — это по сути концепция мьютекса, которым управляют _извне_, а unique_lock — концепция автообъекта, который в большинстве случае _сам_ управляет подчиненным объектом. И хотя сам unique_lock может моделировать _часть_ концепции Lockable, очевидно, у него есть и своя часть контракта, которая определяется только им самим. И именно она не выполняется в указанном мной случае.
ЮЖ>С этой точки зрения — утверждение "unque_lock и locakble — абсолютно разные сущности" неверно.
Верно. Потому что отношение между locable и unique_lock в общем случае никакого отношения к LSP не имеет. По крайней мере, то, что есть исходя из текущей реализации в бусте.
Вот определения из документации:
Specializations of boost::unique_lock model the TimedLockable concept if the supplied Lockable type itself models TimedLockable concept (e.g. boost::unique_lock<boost::timed_mutex>), or the Lockable concept otherwise
и
boost::recursive_mutex implements the Lockable concept
Очевидно, в данном случае unique_lock сужает контракт boost::recursive_mutex, и тогда либо boost::recursive_mutex все же имеет контракт отличный от Lockable, либо все же unique_lock реализует только часть этого контракта. Т.о. документация противоречит сама себе, и на практике Lockable не является подтипом unique_lock, и наоборот — unique_lock также не является подтипом Lockable для случая, когда в качестве Lockable выступает рекурсивный мьютекс.
Но, все это совершенно не влияет на суть рассматриваемого вопроса. На самом деле, совершенно не важно, каким образом связаны контракты сущностей — самое главное, что они различны. Нарушается часть контракта _самого_ unique_lock, которая обеспечивается его состоянием. И после этого он уже не может выполнить контракт Lockable.
Юрий Жмеренецкий:
НИ>>У меня есть подозрения, что тут всё же подразумеваются некие "слабые" концепции, выражающие общие требования к классу без предусловий выполнения этих требований. Т.е. класс удовлетворяет требованиям "слабой" концепции, если существует специфичный для него набор предусловий, при выполнении которых он поддерживает все операции в соответствии с указанной семантикой.
ЮЖ>Для каких целей нужны такие концепции?
Для тех же, что и обычные концепции.
ЮЖ>Да, они описывают поведение, но только в некотором классе состояний [объекта], причем не специфицируют метод определения принадлежности к этому классу. Как c их использованием можно сделать, например, следующее:
ЮЖ>
Данная функция, помимо принадлежности arg к Lockable, должна описывать характер использования arg (впрочем, тогда в ряде случаев можно вообще обойтись без упоминания о Lockable ). Исходя из этого описания пользователь функции должен сделать выводы о том, соблюдаются предусловия для его случая или нет. Фактически "слабая" концепция — это просто незавершённая концепция. Автор класса, формулируя конкретные предусловия, создаёт из неё завершённую концепцию (но без явного имени).
ЮЖ>В той же ситуации, которую мы имеем на текущий момент, повторюсь, введение любого предусловия для метода unique_lock::lock приводит к нарушению LSP.
Только если считать Lockable завершённой концепцией. Ладно, что ты скажешь по поводу следующих двух вопросов?
1) Соответствует ли бустовой концепции Lockable класс std::mutex из N2914 — 30.4.1.1?
2) Может ли этот std::mutex быть использован для инстанцирования boost::unique_lock?
Re[12]: api design: return code or exception - formal criter
Здравствуйте, Andrew S, Вы писали:
ЮЖ>>В оригинальной формулировке LSP дихотомия subtype/supertype не имеет никакого отношения к классическому наследованию. Наследование — один из возможных способов выражения таких отношений.
AS>Кто сказал, что unique_lock и Locable связаны отношением подстановки аналогично приведенному вами А и Б?
Cуществование non member function lock. Приведенная Вами цитата из документации. Хотя хватит даже любой пользовательской шаблонной функции, принимающей Lockable объект.
AS>Для пользователя эти сущности имеют разный контракт.
Определение контрактов для методов Lockable я вижу. Отдельного определения контрактов функций unique_lock::lock, try_lock, unlock — нет. Вместо этого я вижу ссылку на Lockable, и соответственно на контракты соответствующих методов. Вывод очевиден — контракты для всех трех функций у этих двух сущностей одинаковы. Этот вывод получен из документации.
AS>Этак можно сказать, что любые объекты, которые имеют конструктор и деструктор, являются подтипами.
Можно. Другой пример: POD объекты, которые можно копировать с помощью memcpy — substitutability в чистом виде. Или std::advance, принимающая итераторы. Или non member function lock.
AS>Никакого отношения к связи между unique_lock и lockable принцип LSP и наследование, как его реализация, не имеет.
Наследование не является "реализацией" LSP. С помощью наследования можно моделировать subtyping relation, но это далеко не единственная возможность.
AS>Например, вот:
AS>
AS>Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).
AS>Для unique_lock, очевидно, это не выполняется.
Цитата судя по всему из википедии...
А что именно не выполняется — вся фраза целиком? Это и есть нарушение LSP. Desirable properties — это те самые выводы, полученные из документации.
Если же не выполняется какая-то отдельная часть, уточните.
ЮЖ>>С этой точки зрения — утверждение "unque_lock и locakble — абсолютно разные сущности" неверно.
AS>Верно. Потому что отношение между locable и unique_lock в общем случае никакого отношения к LSP не имеет.
Ммм... Не имеет по определению или не имеет, потому что при использовании возникает нарушение и поэтому такое использование не разрешается?
AS>По крайней мере, то, что есть исходя из текущей реализации в бусте.
Издалека:
Вот эта функция корректна:
unsigned int x(unsigned int a, unsigned int b)
{
return a + b;
}
или нет?
Правильный ответ — может быть. Точный ответ зависит от спецификации — если результатом должна быть сумма (как она формулируется для unsigned int) двух чисел — то корректна, иначе — нет. Ваш вывод о наличии дополнительных состояний, выполнения 'частей контракта' получен путем анализа исходного кода/исходя из каких-либо допущений/etc. и для функции 'x' (выше) будет звучать как 'эта функция складывает числа'. Да, она их складывает, но предположим что в документации написано — она их должна перемножать. Кто прав — Вы или документация? Независимо от ответа, функция некорректна — она делает не то, что написано в документации (desirable/provable/etc. properties).
Возвращаясь к нашим концепциям: посмотрите документацию — из нее следует очевидный вывод (см. выше) — контракты для трех функций одинаковы в обоих случаях. Возникает противоречие — мы не можем эксплуатировать задокументированную эквивалентность контрактов в силу реализации. И происходит это именно из-за LSP, т.к. концепция Lockable помимо сигнатур функций специфицирует поведение как набор пред и постусловий. Необходимость наличия своего состояния у unique_lock, к которой Вы аппелируйте, вызвана ничем иным как следствием необходимости выполнения своих, специфических предусловий. А они, в свою очередь, никак не вписываются в Lockable без нарушения LSP.
Re[13]: api design: return code or exception - formal criter
ЮЖ>>>В оригинальной формулировке LSP дихотомия subtype/supertype не имеет никакого отношения к классическому наследованию. Наследование — один из возможных способов выражения таких отношений.
AS>>Кто сказал, что unique_lock и Locable связаны отношением подстановки аналогично приведенному вами А и Б?
ЮЖ>Cуществование non member function lock. Приведенная Вами цитата из документации. Хотя хватит даже любой пользовательской шаблонной функции, принимающей Lockable объект.
AS>>Для пользователя эти сущности имеют разный контракт.
ЮЖ>Определение контрактов для методов Lockable я вижу. Отдельного определения контрактов функций unique_lock::lock, try_lock, unlock — нет. Вместо этого я вижу ссылку на Lockable, и соответственно на контракты соответствующих методов. Вывод очевиден — контракты для всех трех функций у этих двух сущностей одинаковы. Этот вывод получен из документации.
Я вам привел пример, когда контракт Lockable не может быть выполнен. Поскольку документация в этом случае unclear — все остальное говорит реализация.
ЮЖ>Возвращаясь к нашим концепциям: посмотрите документацию — из нее следует очевидный вывод (см. выше) — контракты для трех функций одинаковы в обоих случаях. Возникает противоречие — мы не можем эксплуатировать задокументированную эквивалентность контрактов в силу реализации. И происходит это именно из-за LSP, т.к. концепция Lockable помимо сигнатур функций специфицирует поведение как набор пред и постусловий. Необходимость наличия своего состояния у unique_lock, к которой Вы аппелируйте, вызвана ничем иным как следствием необходимости выполнения своих, специфических предусловий. А они, в свою очередь, никак не вписываются в Lockable без нарушения LSP.
Все что, сказано в документации, что unique_lock моделирует концепт lockable, если инстанцирована им. Но. Реализация и назначение unique_lock показывают, что это не так. Иначе хватило бы просто ассерта. Итого — в топку либо документацию либо реализацию.
Тем не менее. Представим, что реализация делает именно то, что и должна. Изначальный вопрос звучал так — почему тут исключение, а не assert? И то, и то может быть использовано как сигнализатор нарушения контракта. И исключение тут именно потому, что состояние объекта unique_lock после нарушения контракта является не валидным.
В общем, предлагаю закругляться, никаких новых аргументов уже не появляется.
Здравствуйте, Николай Ивченков, Вы писали:
НИ>Юрий Жмеренецкий:
НИ>>>У меня есть подозрения, что тут всё же подразумеваются некие "слабые" концепции, выражающие общие требования к классу без предусловий выполнения этих требований. Т.е. класс удовлетворяет требованиям "слабой" концепции, если существует специфичный для него набор предусловий, при выполнении которых он поддерживает все операции в соответствии с указанной семантикой.
ЮЖ>>Для каких целей нужны такие концепции?
НИ>Для тех же, что и обычные концепции.
Для обычной концепции мне достаточно знать только ее контракт, для слабых — то же самое, плюс контракт 'реализатора'. Чем-то мне это напоминает dynamic_cast...
ЮЖ>>
НИ>Данная функция, помимо принадлежности arg к Lockable, должна описывать характер использования arg (впрочем, тогда в ряде случаев можно вообще обойтись без упоминания о Lockable ). Исходя из этого описания пользователь функции должен сделать выводы о том, соблюдаются предусловия для его случая или нет.
Т.е. фактически предусловия пользователь должен выводить сам. В чуть более сложных случаях — вложенные вызовы, использование в f других таких концепций — это приведет к тому, что ему будет необходимо отследить весь путь выполнения (исходя из его случая). Пока я в этому вижу только минусы...
НИ>Фактически "слабая" концепция — это просто незавершённая концепция. Автор класса, формулируя конкретные предусловия, создаёт из неё завершённую концепцию (но без явного имени).
Чем-то напоминает CRTP. Я имел ввиду более 'худший' вариант: 'динамическую' концепцию, которая определена не статически для типа, а для значений, например после выполнения delete для указателя, последний перестает удовлетворять Assignable requirements. + не замкнутые относительно себя концепции.
ЮЖ>>что ты скажешь по поводу следующих двух вопросов?
НИ>1) Соответствует ли бустовой концепции Lockable класс std::mutex из N2914 — 30.4.1.1? НИ>2) Может ли этот std::mutex быть использован для инстанцирования boost::unique_lock?
Если отвечать сразу — то нет в обоих случаях, хотя бы из-за типов исключений =) Если не принимать это во внимание — то, на первый взгляд, можно ответить да на оба вопроса. Но для точных ответов нужен более тщательный анализ. + некторые формулировки стандарта выглядят достаточно спорно, например
30.4.1/12 (mutext::lock)
Error conditions:
...
— resource_deadlock_would_occur — if the current thread already owns the mutex and is able
to detect it.
30.4.3.2.2/4 (unique_lock::lock)
Error conditions:
...
— resource_deadlock_would_occur — if the current thread already owns the mutex (i.e., on entry,
owns is true).
Пследнее — это ни что иное как завуалированное предусловие, более того — формально, 30.4.3.2.2/2/3 делают невозможным возникновение ситуации c resource_deadlock_would_occur. Хотя я не исключаю что я что-то упустил.
Re[3]: api design: return code or exception - formal criteri
Здравствуйте, crable, Вы писали:
V>>К чему это всё скажите вы? А к тому что иногда поддержка исключений библиотекой приводит к образованию некоторого пользовательского болота, организованного недобросовестными программистами использующими библиотеку, из которого вам впоследствии будет трудно выбраться. V>>Иногда лучше вообще не пользоваться исключениями и "заставлять" пользователя обрабатывать код возврата. C>А мне показалось, что лучше не ставить ассерты перед throw и недобросовестеыми тут выглядят как раз программисты библиотеки, сделавшие это.
Тогда придётся перезапускать приложение с включёнными галками ловить исключения, что тоже не есть хорошо.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[3]: api design: return code or exception - formal criteri
Здравствуйте, Erop, Вы писали:
V>>Иногда лучше вообще не пользоваться исключениями и "заставлять" пользователя обрабатывать код возврата. E>IMHO, пользователей библиотеки, которые "развели болото" не удастся заставить верно обрабатывать коды возврата...
Для начало можно сделать коды возврата обязательными параметрами всех нужных функций.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[15]: api design: return code or exception - formal criter
AS>>В общем, предлагаю закругляться, никаких новых аргументов уже не появляется.
ЮЖ>Ок. обсуждение unique_lock можно считать закрытым. Но у меня еще пара вопросов — считаете ли Вы, что например функция std::string::erase(iterator, iterator) должна проверять итераторы на валидность (и кидать исключение, в случае если дальнейшее выполнение приведет к порче 'состояния')?
В debug time — да. В релизе — нет. Причины
1. Производительность.
2. Нарушение контракта не обеспечит нарушение контракта подчиненного объекта.
В случае unique_lock нарушение контракта ведет к нарушению контракта подчиненного объекта.
ЮЖ>Изменится или нет ответ на этот вопрос в таких случаях: 1) существует предусловие говорящее о том что передаваемые итераторы должны быть валидны (в как это формулируется для объекта string) 2) такое предусловие отсутствует.
Юрий Жмеренецкий:
ЮЖ>Для обычной концепции мне достаточно знать только ее контракт, для слабых — то же самое, плюс контракт 'реализатора'. Чем-то мне это напоминает dynamic_cast...
ЮЖ>>>
НИ>>Данная функция, помимо принадлежности arg к Lockable, должна описывать характер использования arg (впрочем, тогда в ряде случаев можно вообще обойтись без упоминания о Lockable ). Исходя из этого описания пользователь функции должен сделать выводы о том, соблюдаются предусловия для его случая или нет.
ЮЖ>Т.е. фактически предусловия пользователь должен выводить сам. В чуть более сложных случаях — вложенные вызовы, использование в f других таких концепций — это приведет к тому, что ему будет необходимо отследить весь путь выполнения (исходя из его случая). Пока я в этому вижу только минусы...
В N2914 вводятся "Mutex requirements", по сути являющиеся "слабой" концепцией. Эта концепция подразделяется на две: "non-recursive mutex" и "recursive mutex". std::unique_lock инстанцируется типом, соответствующим "слабой" концепции Mutex. Различие в использовании std::unique_lock, инстанцированного non-recursive mutex-ом, и std::unique_lock, инстанцированного recursive mutex-ом, проводится уже в описании его функций-членов:
explicit unique_lock(mutex_type& m);
Precondition: If mutex_type is not a recursive mutex the calling thread does not own the mutex.
По поводу повторного вызова lock у объекта типа std::unique_lock<T> мне пока неясно — то ли там undefined behavior, то ли генерация исключения.
НИ>>1) Соответствует ли бустовой концепции Lockable класс std::mutex из N2914 — 30.4.1.1? НИ>>2) Может ли этот std::mutex быть использован для инстанцирования boost::unique_lock?
ЮЖ>Если отвечать сразу — то нет в обоих случаях, хотя бы из-за типов исключений =)
Ну, в принципе, можно рассмотреть некий MutexWrapper над std::mutex, где стандартные исключения будут транслироваться в бустовские, а всё остальное будет, как в std::mutex.
ЮЖ>Если не принимать это во внимание — то, на первый взгляд, можно ответить да на оба вопроса.
Ну, как же? У std::mutex::lock есть предусловие: эту функцию можно вызывать только если текущий поток не владеет данным мьютексом. Концепция Lockable (в Boost) такого предусловия не содержит.
Re[12]: api design: return code or exception - formal criter
Здравствуйте, Николай Ивченков, Вы писали:
НИ>В N2914 вводятся "Mutex requirements", по сути являющиеся "слабой" концепцией. Эта концепция подразделяется на две: "non-recursive mutex" и "recursive mutex".
Это две разных концепции. Упоминание "If mutex_type is not a recursive mutex" только подтверждает это. Фактически просто switch между двумя концепциями, а не использование одной.
ЮЖ>>Если не принимать это во внимание — то, на первый взгляд, можно ответить да на оба вопроса.
НИ>Ну, как же? У std::mutex::lock есть предусловие: эту функцию можно вызывать только если текущий поток не владеет данным мьютексом.
Где оно сформулировано? пока я вижу только "if a function does not specify any further preconditions, there will be no “Requires” paragraph."
Кроме того есть 17.6.3.11/1, и если ты говоришь что предусловие все же имеется, то должно ли оно удовлетворять 'стандартному' определению?
Плюс, аналога 17.6.3.11/1 для буста нет. Т.е. здесь возможна ситуация двух различных определений предусловий. Да и мое определение предусловий расходится с определением стадарта (они там, кстати, нарушили свой любимый zero-overhead principle).
Re[4]: api design: return code or exception - formal criteri
Здравствуйте, Vain, Вы писали:
V>Для начало можно сделать коды возврата обязательными параметрами всех нужных функций.
А что это даст, кроме дальнейшего снижения удобства использования библиотеки?
IMHO, качественный клинтский код -- это забота его авторов. Библиотека должна позволять писать качественный, и не должна мешать это делать или провоцировать на некачественный. Но бороться?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[13]: api design: return code or exception - formal criter
Юрий Жмеренецкий:
НИ>>В N2914 вводятся "Mutex requirements", по сути являющиеся "слабой" концепцией. Эта концепция подразделяется на две: "non-recursive mutex" и "recursive mutex".
ЮЖ>Это две разных концепции. Упоминание "If mutex_type is not a recursive mutex" только подтверждает это. Фактически просто switch между двумя концепциями, а не использование одной.
Если говорить о типе, которым инстанцируется std::unique_lock, то тут используется именно одна "слабая" концепция Mutex. А то, что такому описанию std::unique_lock можно сопоставить некое эквивалентное по смыслу описание, выраженное через две концепции с более конкретизированными предусловиями — это уже другой вопрос.
ЮЖ>>>Если не принимать это во внимание — то, на первый взгляд, можно ответить да на оба вопроса.
НИ>>Ну, как же? У std::mutex::lock есть предусловие: эту функцию можно вызывать только если текущий поток не владеет данным мьютексом.
ЮЖ>Где оно сформулировано?
30.4.1.1/3:
The behavior of a program is undefined if:
[...]
— a thread that owns a mutex object calls lock() or try_lock() on that object
С точки зрения пользователя std::mutex нет никакой разницы между этим undefined behavior и undefined behavior, возникающим в результате несоблюдения условий, которые были бы явным образом специфицированы как предусловия. Т.е. фактически отсутствие владения мьютексом со стороны текущего потока можно считать предусловием для вызова lock.
ЮЖ>Плюс, аналога 17.6.3.11/1 для буста нет.
Если исходить из того же принципа (появление undefined behavior следует считать следствием нарушения предусловий), и если попытку повторного захвата boost::mutex считать приводящей к undefined behavior (у меня в тестовой программе попытка повторного захвата boost::mutex приводит к зависанию потока), то можно прийти к тем же выводам и относительно boost::mutex.
ЮЖ>они там, кстати, нарушили свой любимый zero-overhead principle.
Где именно?
Re[2]: api design: return code or exception - formal criteri
Здравствуйте, Vain, Вы писали:
V> После появились программисты библиотеки, которые поняли, что каждый раз включать галку в студии останавливаться на исключении в отладчике не катит и вставили ассерт перед самим throw.
Объясните подробнее пожалуйста этот кусок.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[14]: api design: return code or exception - formal criter
Здравствуйте, Николай Ивченков, Вы писали:
НИ>Если говорить о типе, которым инстанцируется std::unique_lock, то тут используется именно одна "слабая" концепция Mutex.
Только вот одного инстанцирования не достаточно.
НИ>А то, что такому описанию std::unique_lock можно сопоставить некое эквивалентное по смыслу описание, выраженное через две концепции с более конкретизированными предусловиями — это уже другой вопрос.
Так в чем здесь преимущества "слабой" концепции? Для спецификации методов std::unique_lock по прежнему используется switch по 'типам', а там где не используется — это простое наследование спецификаций.
Как только мы вводим 'шаблоны' спецификаций (а это и есть по сути 'слабая' концепция), наследование, и т.п. — автоматом получаем проблемы, аналогичные проблемам, возникающим при обычном использовании 'типов'. Лишние зависимости, хрупкие/толстые/etc. базовые интерфейсы, нарушения LSP, и т.д.
ЮЖ>>Где оно сформулировано?
НИ>30.4.1.1/3: НИ>
The behavior of a program is undefined if:
НИ>[...]
НИ>— a thread that owns a mutex object calls lock() or try_lock() on that object
НИ>С точки зрения пользователя std::mutex нет никакой разницы между этим undefined behavior и undefined behavior, возникающим в результате несоблюдения условий, которые были бы явным образом специфицированы как предусловия.
17.6.3.11/1:
Violation of the preconditions specified in a function’s Required behavior: paragraph results in undefined
behavior unless the function’s Throws: paragraph specifies throwing an exception when the precondition is
violated.
Согласно этой формулировке — несоблюдение предусловий не всегда UB. В этом ракурсе очень интересно выглядит описание error conditions для mutex::lock. Когда я говорил о "завуалированных" предусловиях — я имел ввиду именно это.
НИ>Т.е. фактически отсутствие владения мьютексом со стороны текущего потока можно считать предусловием для вызова lock.
Хорошо — будем считать.
ЮЖ>>они там, кстати, нарушили свой любимый zero-overhead principle.
НИ>Где именно?
Здесь: "throwing an exception when the precondition is violated" — разрешается диагностировать ситуацию, при которой дальнейшее выполнение приводит к UB и возбуждать исключения. Там где это проиходит и нарушается zero-overhead principle, в частности во всех местах (где "precondition is violated") возбуждения стандартной библиотекой исключений, производных от std::logic_error.
Re[15]: api design: return code or exception - formal criter
Юрий Жмеренецкий:
НИ>>А то, что такому описанию std::unique_lock можно сопоставить некое эквивалентное по смыслу описание, выраженное через две концепции с более конкретизированными предусловиями — это уже другой вопрос.
ЮЖ>Так в чем здесь преимущества "слабой" концепции?
Я не знаю, какие преимущества здесь видели авторы, но факт её применения, IMHO, налицо.
НИ>>С точки зрения пользователя std::mutex нет никакой разницы между этим undefined behavior и undefined behavior, возникающим в результате несоблюдения условий, которые были бы явным образом специфицированы как предусловия.
ЮЖ>
17.6.3.11/1:
ЮЖ>Violation of the preconditions specified in a function’s Required behavior: paragraph results in undefined
ЮЖ>behavior unless the function’s Throws: paragraph specifies throwing an exception when the precondition is
ЮЖ>violated.
ЮЖ>Согласно этой формулировке — несоблюдение предусловий не всегда UB.
Я и не говорил, что несоблюдение предусловий всегда влечёт undefined behavior. Достаточно и того, что иногда undefined behavior приводится как следствие несоблюдения предусловий. Если существуют критерии невозникновения undefined behavior, то с практической точки зрения не имеет никакого значения, обозначает ли документация эти критерии в качестве некоторых предусловий явным образом.
ЮЖ>В этом ракурсе очень интересно выглядит описание error conditions для mutex::lock. Когда я говорил о "завуалированных" предусловиях — я имел ввиду именно это.
Я вообще затрудняюсь сказать, в чём заключается суть "resource_deadlock_would_occur — if the current thread already owns the mutex and is able to detect it" и когда это может быть актуально.
НИ>>Т.е. фактически отсутствие владения мьютексом со стороны текущего потока можно считать предусловием для вызова lock.
ЮЖ>Хорошо — будем считать.
Если считать Lockable (в Boost) завершённой концепцией, то std::mutex и boost::mutex не соответствуют этой концепции, т.к. std::mutex и boost::mutex предъявляют более жёсткие предусловия.
ЮЖ>>>они там, кстати, нарушили свой любимый zero-overhead principle.
НИ>>Где именно?
ЮЖ>Здесь: "throwing an exception when the precondition is violated" — разрешается диагностировать ситуацию, при которой дальнейшее выполнение приводит к UB и возбуждать исключения. Там где это происходит и нарушается zero-overhead principle, в частности во всех местах (где "precondition is violated") возбуждения стандартной библиотекой исключений, производных от std::logic_error.
basic_string(const basic_string<charT,traits,Allocator>& str,
size_type pos, size_type n = npos,
const Allocator& a = Allocator());
Requires: pos <= str.size()
Throws: out_of_range if pos > str.size().
Где тут overhead? IMHO, тут проверка предусловия занимает очень незначительную часть времени от общих затрат на конструирование, так что её трудно назвать overhead-ом. Использование механизма исключений всё равно необходимо, т.к. у аллокатора нет иного способа сообщения об ошибке выделения памяти.
Re[16]: api design: return code or exception - formal criter
Здравствуйте, Николай Ивченков, Вы писали:
ЮЖ>>>>они там, кстати, нарушили свой любимый zero-overhead principle.
НИ>>>Где именно?
ЮЖ>>Здесь: "throwing an exception when the precondition is violated" — разрешается диагностировать ситуацию, при которой дальнейшее выполнение приводит к UB и возбуждать исключения. Там где это происходит и нарушается zero-overhead principle, в частности во всех местах (где "precondition is violated") возбуждения стандартной библиотекой исключений, производных от std::logic_error.
НИ>
basic_string(const basic_string<charT,traits,Allocator>& str,
НИ> size_type pos, size_type n = npos,
НИ> const Allocator& a = Allocator());
НИ>Requires: pos <= str.size()
НИ>Throws: out_of_range if pos > str.size().
НИ>Где тут overhead? IMHO, тут проверка предусловия занимает очень незначительную часть времени от общих затрат на конструирование, так что её трудно назвать overhead-ом.
Смысл принципа: не хочу — не плачу. Если у меня из контекста использования автоматически вытекает выполнения предусловия — зачем мне нужна проверка "внутри"? На всякий случай? Нарушение в том, что в этом случае у меня нет рычага для ее отключения. А насколько это бьет по производительности — совершенно неважно.
НИ>Использование механизма исключений всё равно необходимо, т.к. у аллокатора нет иного способа сообщения об ошибке выделения памяти.
Ты хочешь сказать что аллокатор кидает out_of_range? Это неверно. Он кидает bad_alloc — и к этому у меня претензий нет. А вот out_of_range к аллокатору не имеет никакого отношения.
Re[2]: api design: return code or exception - formal criteri
Здравствуйте, jazzer, Вы писали:
J>все функции в двух экземплярах, одни бросают, другие (у которых есть параметр boost::system::error_code & ec) — не бросают.
Проще объединить, что и предложили в N2838:
Здравствуйте, byleas, Вы писали:
B>Здравствуйте, jazzer, Вы писали:
J>>все функции в двух экземплярах, одни бросают, другие (у которых есть параметр boost::system::error_code & ec) — не бросают. B>Проще объединить, что и предложили в N2838:
Мы уже с Quasi это обсуждали, я по второму разу не буду
ЮЖ>Смысл принципа: не хочу — не плачу. Если у меня из контекста использования автоматически вытекает выполнения предусловия — зачем мне нужна проверка "внутри"? На всякий случай? Нарушение в том, что в этом случае у меня нет рычага для ее отключения. А насколько это бьет по производительности — совершенно неважно.
Ну, если брать это за аксиому...
НИ>>Использование механизма исключений всё равно необходимо, т.к. у аллокатора нет иного способа сообщения об ошибке выделения памяти.
ЮЖ>Ты хочешь сказать что аллокатор кидает out_of_range?
Нет, я хочу сказать, что, устранив throw, соответствующий проверке предусловия pos <= str.size(), мы всё равно не получим конструктор, не генерирующий исключений (для которого компилятор потенциально мог бы сгенерировать более оптимальный код).
Re[4]: api design: return code or exception - formal criteri
Здравствуйте, Vain, Вы писали:
V>Здравствуйте, crable, Вы писали:
V>>>К чему это всё скажите вы? А к тому что иногда поддержка исключений библиотекой приводит к образованию некоторого пользовательского болота, организованного недобросовестными программистами использующими библиотеку, из которого вам впоследствии будет трудно выбраться. V>>>Иногда лучше вообще не пользоваться исключениями и "заставлять" пользователя обрабатывать код возврата. C>>А мне показалось, что лучше не ставить ассерты перед throw и недобросовестеыми тут выглядят как раз программисты библиотеки, сделавшие это. V>Тогда придётся перезапускать приложение с включёнными галками ловить исключения, что тоже не есть хорошо.
Во первых, каким образом использование кодов возврата вместо исключений поможет решить проблему с ассертами?
Во-вторых, в чём же всё-таки были виноваты пользователи билиотеки?
И в третьих, ты сам описал ситуацию, при которой эти ассерты только мешают, более того, я, например, склонен считать, что так будет в большинстве случаев. Добалять ассерты только потому, что кому-то трудно раз в сто лет заставить студию перехватывать исключения выглядит, по меньшей мере нелепо. Кстати, для того, чтобы поставить эту галку вовсе необязятельно перезапускать приложение.
The last good thing written in C was Franz Schubert's Symphony No. 9.
Re[19]: api design: return code or exception - formal criter
Юрий Жмеренецкий:
ЮЖ>>>Смысл принципа: не хочу — не плачу. Если у меня из контекста использования автоматически вытекает выполнения предусловия — зачем мне нужна проверка "внутри"? На всякий случай? Нарушение в том, что в этом случае у меня нет рычага для ее отключения.
Можно привести пример, где предоставляется выбор: operator[] и at.
ЮЖ>Меня больше интересует поведение клиента в том случае, когда он не смог обеспечить выполнение предусловий. Могу ли я в этом случае (как реализующий функцию) переводить программу в состояние 'сгенерированно исключение'? Клиент не выполнил своего контракта — как я могу ожидать от него готовности к такой ситуации? Если он готов обработать такую ситуацию — почему он сразу не выполнил предусловия?, он ведь заинтересован в результате. Возбудив исключение — я просто замаскирую дефект, логическую ошибку программиста (клиента). Логическую — не потому что logic_error, а потому что это именно ошибка в логике, неспособность понять формулу 'если P то Q'. Такие ошибки надо исправлять (не допускать), а не обрабатывать. Есть еще некоторые проблемы, но они уже являются производными от этой.
Видимо, это такая своеобразная замена assert-у. Предполагается, что программист должен отлавливать исключения std::logic_error (и производные от него) и трактовать их как внутренние ошибки в программе. Для release-сборки это приемлемо, а вот для debug-сборки assert-ы, вероятно, предпочтительнее (хотя я где-то читал, что есть люди, практикующие компоновку debug- и release-модулей, и для них использование assert-ов в шаблонах и inline-функциях оборачивается нарушением ODR).
Re[6]: api design: return code or exception - formal criteri
Здравствуйте, Vain, Вы писали:
V>Одна библиотека не сможет этого обеспечить, нужны ещё нормальные программисты, которые её будут правильно использовать. А на практике, обычно, это не выполняется. Поэтому, иногда, лучше не использовать исключения, которые приводят к болоту, а использовать только коды возврата. При этом пользователь не сможет написать рабочий код без проверки кода возврата, что есть защита от дурака.
Во-первых, болото можно легко развести и при исключениях и при кодах возврата. IMHO, при кодах развести проще, так как их проще игнорить. Твой способ "форсировать" проверки элементарно обходится примерно так:
// пусть где-то в хедере библиотеки объявлено:
// void LibFunc( T1 arg1, T2 arg2, T3 arg3, LIB_RESULT& funcRes );
// Дальше пишем в своём хедере так:extern LIB_RESULT libResult;
void LibFunc( T1 arg1, T2 arg2, T3 arg3, LIB_RESULT& funcRes = libResult );
void
Чем делаем контроль факультативным.
А можно и так:
чем делаем контроль не только факультативным, но и удобным...
Это конечно всё ССЗБ, но и asserts писать где попало, это тоже ССЗБ...
Так что, IMHO, если уж ставится задача форсировать обработку ошибок, то надо таки исключения бросать...
С другой стороны, если тебе уж кажется, что так важно поддержать твой сценарий, то можно сделать как-то так:
ну и предоставляешь снаружи библиотеки доступ к установке/съёму ShouldThrowItCallback, а в библиотеке вместо throw xxx(x, y, z); пишешь throwIfShould( xxx(x, y, z) );...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[20]: api design: return code or exception - formal criter
Здравствуйте, Николай Ивченков, Вы писали:
НИ>Можно привести пример, где предоставляется выбор: operator[] и at.
Это лучше чем один, потенциально кидающий вариант. Все равно, в местах где индекс, передаваемый в at, получен не от клиента (пусть будет api::read_index) и клиент не может убедиться в том, что индекс валиден — такая ситуация будет скорее всего расценена как невозможность выполнения постусловий при выполнененных предусловиях. А это не логическая ошибка. Т.е. при использовании at в этом случае нужно поймать исключение и преобразовать его в std::runtime_error (или наследника). Только намного логичнее сразу проверить этот индекс и возбудить std::runtime_error при необходимости. В этом случае можно сразу использовать operator[].
ЮЖ>>Меня больше интересует поведение клиента в том случае, когда он не смог обеспечить выполнение предусловий. Могу ли я в этом случае (как реализующий функцию) переводить программу в состояние 'сгенерированно исключение'? Клиент не выполнил своего контракта — как я могу ожидать от него готовности к такой ситуации? Если он готов обработать такую ситуацию — почему он сразу не выполнил предусловия?, он ведь заинтересован в результате. Возбудив исключение — я просто замаскирую дефект, логическую ошибку программиста (клиента). Логическую — не потому что logic_error, а потому что это именно ошибка в логике, неспособность понять формулу 'если P то Q'. Такие ошибки надо исправлять (не допускать), а не обрабатывать. Есть еще некоторые проблемы, но они уже являются производными от этой.
НИ>Видимо, это такая своеобразная замена assert-у.
Нельзя рассматривать исключения как замену assert-у. Кроме того, если быть последовательным, тогда нужно таким же образом проверять все предусловия и все постусловия.
НИ>Предполагается, что программист должен отлавливать исключения std::logic_error (и производные от него) и трактовать их как внутренние ошибки в программе.
Для чего? для отлова багов? Единственный возможный вариант при диагностике такой ситуации — вызов abort в месте обнаружения. Только для полного счастья, повторюсь, необходимо проверять все предусловия и все постусловия. А это приведет к катастрофическому снижению производительности. Если же возбуждать logic_error (вместо вызова abort), то nothrow функции с пред и постусловиями волшебным образом получают возможность возбуждать исключения, соответственно их использование намного усложняется.
НИ>Для release-сборки это приемлемо
Не могу с этим согласиться.
Re[21]: api design: return code or exception - formal criter
Юрий Жмеренецкий:
ЮЖ>Нельзя рассматривать исключения как замену assert-у. Кроме того, если быть последовательным, тогда нужно таким же образом проверять все предусловия
Какое этому обоснование?
НИ>>Предполагается, что программист должен отлавливать исключения std::logic_error (и производные от него) и трактовать их как внутренние ошибки в программе.
ЮЖ>Для чего? для отлова багов? Единственный возможный вариант при диагностике такой ситуации — вызов abort в месте обнаружения.
Хорошо, у пользователя программа упала по abort. Разработчику нужно пофиксить багу. Как ты предлагаешь выяснять примерную причину ошибки на основе тех данных, которые среднестатистический пользователь в состоянии предоставить техподдержке?
ЮЖ>Только для полного счастья, повторюсь, необходимо проверять все предусловия и все постусловия. А это приведет к катастрофическому снижению производительности.
Нужно найти разумный компромисс.
ЮЖ>Если же возбуждать logic_error (вместо вызова abort), то nothrow функции с пред и постусловиями волшебным образом получают возможность возбуждать исключения, соответственно их использование намного усложняется.
Если функция может выпускать наружу исключения, то она уже не является nothrow. Конечно, это может усложнить её использование, но зато взамен мы получаем какую-то информацию об ошибке.
Re[4]: api design: return code or exception - formal criteri
Здравствуйте, Erop, Вы писали:
E>При этом можно его сделать таким странным, что он может его бросать в случае если передали специально сконструированный экземпляр.
Да, кстати, про константную ссылку я погорячился, конечно.
Вполне можно делать и неконстантную. Просто из throws() возвращать ссылку на статический объект и всё...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[23]: api design: return code or exception - formal criter
Юрий Жмеренецкий:
ЮЖ>>>Нельзя рассматривать исключения как замену assert-у. Кроме того, если быть последовательным, тогда нужно таким же образом проверять все предусловия
НИ>>Какое этому обоснование?
ЮЖ>Иначе, при таком подходе (очень плохом, имхо), нельзя обнаружить все ошибки (логические).
Очень туманное объяснение.
НИ>>Хорошо, у пользователя программа упала по abort. Разработчику нужно пофиксить багу. Как ты предлагаешь выяснять примерную причину ошибки
ЮЖ>Это другой вопрос (решаемый в принципе).
Этот вопрос напрямую связан с выбором между throw и немедленным abort. Где-то, конечно, можно вести очень подробные логи — тыкать логирование в каждую функцию, — но это изврат. Или под "решаемый в принципе" подразумевается тщательный review всего кода?
ЮЖ>Разница между возбуждением исключения и abort в этом случае — нет гарантий, что размотка стека не приведет к инициации других ошибок
И что?
ЮЖ>нет гарантий что все данные останутся в порядке
А в случае внезапного abort несохранённые данные вообще будут безвозвратно утеряны. Хорошенькое дельце, ничего не скажешь.
ЮЖ>нет гарантий что обработчик исключений не содержит багов
Если так рассуждать, то пользователю вообще не следует запускать программу, т.к. изначально нет никакой гарантии, что она не содержит багов.
ЮЖ>abort выводит программу из состояния 'баг' с минимальными последствиями.
Потеря несохранённых данных — это, по-твоему, минимальные последствия?
ЮЖ>Если же требуется все-таки не падать даже при наличии багов — то все эти проблемы придется все-таки решить. ЮЖ>Но здесь возникает забавное логичское противоречие — если программист может решить эти проблемы не наплодив новых багов — почему он не может их не плодить изначально?
Код обработчика логической ошибки программы может быть гораздо короче, чем весь код, для которого эта ошибка отлавливается. Меньший объём кода -> меньшая вероятность ошибок.
ЮЖ>>>Только для полного счастья, повторюсь, необходимо проверять все предусловия и все постусловия. А это приведет к катастрофическому снижению производительности.
НИ>>Нужно найти разумный компромисс.
ЮЖ>Разумый выход — снижение количества потенциально опасных мест, которые могут привести к остановке (defect -> fault). При большом количестве дефектов (сам по себе дефект не всегда приводит к остановке) появляется проблема с их идентификацией, которую ты озвучил. Вместо строительства подсистемы ловли багов логичнее попытаться снизить общее количество мест их возникновения. Вот здесь как раз можно эксплуатировать ценное свойство пред/пост условий — выводимость из контекста. Не нужно выполнять повторную проверку, там, где ее выполнение выводимо из контекста. И никакой ошибки в этом случае не возникнет.
Выводимо оно из контекста или нет, решает программист. Решение программиста может быть неправильным.
ЮЖ>Опять тот же вопрос — т.е. программист обладает достаточной квалификацией, для того чтобы писать код, устойчивый к исключениям в любых местах (+ допускаются неизбежные дополнительные затраты по ресурсам), а проверить предусловия для вызова функции он не в состоянии?
Программист может быть уверен (ошибочно), что эти предусловия всегда соблюдаются. Зачем тогда он станет их проверять? А если и проверит, то что будет делать в случае обнаружения несоответствия (в release-сборке)?
ЮЖ>Чуть-чуть усложнили здесь, чуть-чуть усложнили там
Как часто происходит такое усложнение? Отмечу, что генерация исключения не лишает нас возможности произвести тот же abort в любое другое время после обработки ошибки. И даже если в процессе раскрутки стека мы получим какие-нибудь неразрушительные ошибки вроде утечки памяти, это не будет иметь никакого значения, коль скоро программа всё равно будет аварийно завершена.
ЮЖ>PS: Кроме того, всю (а не 'какую-то') необходимую информацию можно получить куда проще — она (вместве с контекстом) доступна перед вызовом.
И куда её девать? Логировать? А как это скажется на производительности? Какой величины логи мы будем получать? И в любом ли коде у нас вообще есть возможность записывать что-то в лог?
Re[6]: api design: return code or exception - formal criteri
[...]
V>Во первых, в достаточно сложном приложении, включать по-умолчанию ловлю ассертов вообще не преемлемо, т.к. ассерты начинают летать в приложении с самого его запуска и вы постоянно будете на них натыкаться в отладчике.
-1
V>Поэтому, ассерт вставляют перед исключением, чтобы "поймать падение" с первого раза и не мучится с перезапуском приложения и приведением его в состояние перед падением.
Ммм... breakpoint в конструкторе исключения?
Re[6]: api design: return code or exception - formal criteri
Здравствуйте, Vain, Вы писали:
V>Здравствуйте, crable, Вы писали:
[snip]
C>>Во первых, каким образом использование кодов возврата вместо исключений поможет решить проблему с ассертами? V>Оно и не должно. Оно решает проблему с конкретными функциями, где кидание исключений не приемлема.
Может наконец напишешь, что же это за функции и ситуации, в которых кидание исключений неприемлимо? В твоём первоначальном сообщении ничего подобного не видно.
C>>Во-вторых, в чём же всё-таки были виноваты пользователи билиотеки? V>В написании говнокода и непотребщины, которое пришлось переписывать естественно.
это никак не следует.
C>>И в третьих, ты сам описал ситуацию, при которой эти ассерты только мешают, V>Они не мешали, они стали мешать из-за бросания исключений оттуда, откуда можно было вернуть код возврата.
Можешь пояснить? Почему они стали мешать из-за бросания исключений и не мешали бы при использовании кодов возврата? Небольшой пример тоже не помешал бы.
C>>более того, я, например, склонен считать, что так будет в большинстве случаев. Добалять ассерты только потому, что кому-то трудно раз в сто лет заставить студию перехватывать исключения выглядит, по меньшей мере нелепо. V>Во первых, в достаточно сложном приложении, включать по-умолчанию ловлю ассертов вообще не преемлемо, т.к. ассерты начинают летать в приложении с самого его запуска и вы постоянно будете на них натыкаться в отладчике.
Может быть не "ассертов", а "исключений"?
V>Во-вторых, когда приложение "грохается" на исключении, чтобы воспроизвести падение, надо как минимум привести приложение в то состояние, в котором оно было перед самым падением, а это иногда оччень не просто, из-за количества действий которое нужно сделать и раннее включение ловли исключений этому будет только мешать.
Что, в данном контесте, значит "грохается"? Выбрасывает исключение, которое нигде не обрабатывается?
V>Поэтому, ассерт вставляют перед исключением, чтобы "поймать падение" с первого раза и не мучится с перезапуском приложения и приведением его в состояние перед падением.
C>>Кстати, для того, чтобы поставить эту галку вовсе необязятельно перезапускать приложение. V>А исключение уже проскочило и не было поймано, поэтому придётся.
The last good thing written in C was Franz Schubert's Symphony No. 9.
Re[25]: api design: return code or exception - formal criter
Здравствуйте, Юрий Жмеренецкий, Вы писали:
ЮЖ>Тогда объясни — _зачем_ нужно принудительно проверять предусловия и каким-то образом (отличным от assert) реагировать на их нарушение? Для решения каких проблем это используется?
Затем, что есть рефакторинг...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[24]: api design: return code or exception - formal criter
Здравствуйте, Erop, Вы писали:
E>Не правда ваша. E>1) обычный assert приводит к тому, что программа делает красиво ручкой, а хотелось бы ещё и документик сохранить, например
assert не предназначен для продуктивного использования.
E>2) assert, который провалился у пользователя, ситуация неприятная, но, увы, возможная. Дальше всё упирается в простой вопрос кто за что готов платить Если речь идёт о простом таком пользователе-человеке, то ему хорошо бы получить как-то просто после этого инструкции как решить его проблему. А разработчику хорошо бы как-то получить описание возникшей у пользователя ситуации, приведшей к проявлению ошибки...
assert не должен появняться у пользователя.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[2]: api design: return code or exception - formal criteri
Здравствуйте, minorlogic, Вы писали:
M>пример ошибки выполнения. M>bool fileExist(...);
M>Во время выполнения сетевой ресурс на который ссылается путь был отключен, это ошибка выполнения fileExist, и вполнен нормально в таком случае кинуть исключение.
ИМХО, неудачный пример. Если ресурс был отключен (или не существовал), следовательно, file is not exist, следовательно, false? Кто-то выше уже сказал, что надо исходить из потребностей клиентского кода. Если клиентскому коду все равно, отключен ресурс или файла нет в природе, а ему нужно просто знать, может он или не может работать с этим файлом, то исключения будут только мешать. Хотя и тут тоже не без тонкостей: файл может существовать, но прав доступа у нас может и не быть...
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Re[3]: api design: return code or exception - formal criteri
Здравствуйте, slava_phirsov, Вы писали:
_>ИМХО, неудачный пример. Если ресурс был отключен (или не существовал), следовательно, file is not exist, следовательно, false? Кто-то выше уже сказал, что надо исходить из потребностей клиентского кода. Если клиентскому коду все равно, отключен ресурс или файла нет в природе, а ему нужно просто знать, может он или не может работать с этим файлом, то исключения будут только мешать. Хотя и тут тоже не без тонкостей: файл может существовать, но прав доступа у нас может и не быть...
Придумайте пример лучше, мне кажется идею вы уловили.
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[4]: api design: return code or exception - formal criteri
Здравствуйте, minorlogic, Вы писали:
M>Придумайте пример лучше, мне кажется идею вы уловили.
Лично мне не хватает исключений в арифметике (напр, тот же самый atan2(0, 0), или переполнение, или underflow). Вот крутится вычислительный алгоритм (например, решение системы нелинейных дифуров, описывающих работу электронной схемы), в котором в определенной ситуации, при некоторых исходных данных, может возникнуть какая-либо из перечисленных проблем. ИМХО, если бы он сразу прервал вычисления, честно сказав "ну ни х.. себе", как та японская лазерная пила из анекдота, это было бы лучше проверок errno. Хотя, может я уже окончательно отстал от жизни, и в новом стандарте
asin(-2)
или
char x = 0xFF;
x *= 2;
кидают исключения?
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Re[25]: api design: return code or exception - formal criter
Здравствуйте, minorlogic, Вы писали:
M>assert не предназначен для продуктивного использования.
поэтому приходится иметь свой аналог, так как поведение стандартного assert не настраиваемо...
M>assert не должен появняться у пользователя.
Это всего лишь благое пожелание...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[26]: api design: return code or exception - formal criter
Здравствуйте, minorlogic, Вы писали:
M>Здравствуйте, Юрий Жмеренецкий, Вы писали:
ЮЖ>>Везде при подобном коде:
ЮЖ>>
/// \pre x > 10, p != NULL
ЮЖ>>void f(int x, int* p)
ЮЖ>>{
ЮЖ>> assert(x > 10);
ЮЖ>> assert(p);
ЮЖ>> if(x > 10)
ЮЖ>> throw std::invalid_argument("f::x"); // В клинических случаях добавляется запись в лог, etc.
ЮЖ>> // Тип исключения в принципе не так важен, но это по
ЮЖ>> // сути логическая ошибка (std::logic_error).
ЮЖ>> // Иногда можно увидеть std::runtime_error - но это уже похоже на саботаж
ЮЖ>> if(!p)
ЮЖ>> throw std::invalid_argument("f::p");
ЮЖ>> //...
ЮЖ>>}
M>Кажется я начинаю понимать, у вас другая терминология.
Вряд-ли. Я показал пример недопустимого ни под каким предлогом для меня кода, но встречающегося на каждом шагу. Там из контекста обсуждения должно (как я надеялся) быть понятно что преобразование assert'a в исключение — абсолютное непонимание происходящего.
M>Вы пытаетесь проверить DBC входных и т.п. аргументов. Для этих целей лучше написать нормальный обработчик типа CHECK_DC(...); CHECK_PRE_DC(...); CHECK_POST_DC(...); и так далее.
Я понимаю о чем речь. Только в этом коде моего — только то, что я его показал.
M>Культура же програмирования на С++ подразумевает использования макроса ASSERT (assert) для проверки корректности програмым. Т.е. это проверка которая должна срабатывать ВСЕГДА.
Я знаю это и полностью согласен.
Re[24]: api design: return code or exception - formal criter
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, Юрий Жмеренецкий, Вы писали:
ЮЖ>>Иначе, при таком подходе (очень плохом, имхо), нельзя обнаружить все ошибки (логические).
E>Вообще-то нормальные программы ещё что-то и делают, так что ошибки в них можно найти и по результатам их деятельности
До обсуждения предусловий мы с Николаем еще не дошли...
ЮЖ>>Это другой вопрос (решаемый в принципе). Разница между возбуждением исключения и abort в этом случае — нет гарантий, что размотка стека не приведет к инициации других ошибок, нет гарантий что все данные останутся в порядке, нет гарантий что обработчик исключений не содержит багов, и т.д. abort выводит программу из состояния 'баг' с минимальными последствиями. Если же требуется все-таки не падать даже при наличии багов — то все эти проблемы придется все-таки решить.
E>Не правда ваша. E>1) обычный assert приводит к тому, что программа делает красиво ручкой, а хотелось бы ещё и документик сохранить, например
Не обязательно.
E>2) assert, который провалился у пользователя, ситуация неприятная, но, увы, возможная.
Только если у него Debug версия
E>4) Я не знаю ситуаций, когда обработка ошибки совсем не важна. Если уж прога упала, то кто-то что-то должен написать заранее такое, чтобы жизнь продолжилась.
Программа может упасть из-за многих причин, которые не зависят от программиста. Здесь обсуждались только логические ошибки.
ЮЖ>>Но здесь возникает забавное логичское противоречие — если программист может решить эти проблемы не наплодив новых багов — почему он не может их не плодить изначально?
E>Потому, что при разработке системы обработки ошибок, её обычно проектируют и тестируют.
А для всего остального это необязательные шаги?
E>Кроме того, во многих случаях пред/пост-условие трудно не то, что бы проверить, но даже хотя бы и выписать формально...
Если не далать ляпов, то все они проверяемы — иначе теряется смысл. (Делать для функции выделения памяти предусловием существование блока необходимого размера(или более) в большинстве случаев — абсурд). Вот где есть проблема — так это в неполной идентификации постусловий.
E>Если assert кидается исключениями
Без хаков такого не бывает.
Re[27]: api design: return code or exception - formal criter
Здравствуйте, Юрий Жмеренецкий, Вы писали:
ЮЖ>Здравствуйте, minorlogic, Вы писали:
M>>Здравствуйте, Юрий Жмеренецкий, Вы писали:
M>>Культура же програмирования на С++ подразумевает использования макроса ASSERT (assert) для проверки корректности програмым. Т.е. это проверка которая должна срабатывать ВСЕГДА.
ЮЖ>Я знаю это и полностью согласен.
Прошу прощения , я не уследил за контекстом разговора.
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[7]: api design: return code or exception - formal criteri
Здравствуйте, crable, Вы писали:
C>>>И в третьих, ты сам описал ситуацию, при которой эти ассерты только мешают, V>>Они не мешали, они стали мешать из-за бросания исключений оттуда, откуда можно было вернуть код возврата. C>Можешь пояснить? Почему они стали мешать из-за бросания исключений и не мешали бы при использовании кодов возврата? Небольшой пример тоже не помешал бы.
Конструстор некоторого объекта, к примеру, арки по 3м точкам. Если три точки лежат, на одной прямой — то нужен код возврата который скажет, что центр арки найти невозможно. Пользователи библиотеки начинают пользовать конструктор прям в выражениях ничего не проверяя — работает до поры до времени.
C>>>более того, я, например, склонен считать, что так будет в большинстве случаев. Добалять ассерты только потому, что кому-то трудно раз в сто лет заставить студию перехватывать исключения выглядит, по меньшей мере нелепо. V>>Во первых, в достаточно сложном приложении, включать по-умолчанию ловлю ассертов вообще не преемлемо, т.к. ассерты начинают летать в приложении с самого его запуска и вы постоянно будете на них натыкаться в отладчике. C>Может быть не "ассертов", а "исключений"?
Очепатался
V>>Во-вторых, когда приложение "грохается" на исключении, чтобы воспроизвести падение, надо как минимум привести приложение в то состояние, в котором оно было перед самым падением, а это иногда оччень не просто, из-за количества действий которое нужно сделать и раннее включение ловли исключений этому будет только мешать. C>Что, в данном контесте, значит "грохается"? Выбрасывает исключение, которое нигде не обрабатывается?
Я ниже написал про это: C>>>Кстати, для того, чтобы поставить эту галку вовсе необязятельно перезапускать приложение. V>>А исключение уже проскочило и не было поймано, поэтому придётся.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[7]: api design: return code or exception - formal criteri
Здравствуйте, Юрий Жмеренецкий, Вы писали:
V>>Во первых, в достаточно сложном приложении, включать по-умолчанию ловлю ассертов вообще не преемлемо, т.к. ассерты начинают летать в приложении с самого его запуска и вы постоянно будете на них натыкаться в отладчике. ЮЖ>-1
Там речь про исключения была V>>Поэтому, ассерт вставляют перед исключением, чтобы "поймать падение" с первого раза и не мучится с перезапуском приложения и приведением его в состояние перед падением. ЮЖ>Ммм... breakpoint в конструкторе исключения?
Тоже самое, что включить тип исключения в его ловлю "по-умолчанию".
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[9]: api design: return code or exception - formal criteri
Здравствуйте, jazzer, Вы писали:
J>Главная проблема с возвращаемыми значениями — это то, что они игнорируются по умолчанию (в отличие от исключений).
Да, в подавляющем большинстве кода, использующего возвращаемые значения, это огромная проблема. И усугубляется это тем, что подобного рода баги при нормальной работе программы не проявляются, а только когда происходит какая-нибудь очень редкая ситуация. На моей практике был прямо смехотворный случай, где из-за одной единственной пропущенной проверки embedded device входил в такое состояние, из которого его можно было вывести только с помощью hard reset-а с потерей всех данных.
Но в принципе, с возвращаемыми кодами ошибок можно сделать так, что если нет проверки, то вываливается assert (или сразу core dump). Alexandrescu описал один из вариантов реализации этой идеи в CUJ: http://www.ddj.com/cpp/184401917.
Ещё одна проблема с возвращаемыми кодами ошибок — это то, что они не позволяют возвращать нормальный результат работы функции через return. Приходится вводить out parameters, которые страдают своими проблемами. Я в своем проекте, где категорически нельзя было использовать исключения, даже написал такой класс, который совмещал код ошибки с результатом функции, result<T> (наподобие boost::optional<T>), чтобы можно было хоть как-то нормально возвращать результат.
Re[10]: api design: return code or exception - formal criter