Поведение по умолчанию assert
в C ++ - ничего не делать в сборках релиза. Я предполагаю, что это сделано из соображений производительности и, возможно, чтобы пользователи не видели неприятных сообщений об ошибках.
Однако я бы поспорил, что те ситуации, когда an сработал assert
бы, но был отключен, еще более проблематичны, потому что тогда приложение, вероятно, будет аварийно завершать работу еще хуже, потому что какой-то инвариант был сломан.
Кроме того, аргумент производительности для меня имеет значение только тогда, когда это измеримая проблема. Большинство assert
с в моем коде не намного сложнее, чем
assert(ptr != nullptr);
который будет иметь небольшое влияние на большую часть кода.
Это приводит меня к вопросу: должны ли утверждения (означающие концепцию, а не конкретную реализацию) быть активными в сборках релиза? Почему нет)?
Обратите внимание, что этот вопрос не о том, как включить утверждения в сборках релиза (например, #undef _NDEBUG
или использовать реализацию самоопределения). Кроме того, речь идет не о включении утверждений в коде сторонней / стандартной библиотеки, а в коде, контролируемом мной.
источник
Ответы:
Классика
assert
- это инструмент из старой стандартной библиотеки C, а не из C ++. Это все еще доступно в C ++, по крайней мере, по причинам обратной совместимости.У меня нет точной временной шкалы стандартных библиотек C под рукой, но я уверен, что она
assert
была доступна вскоре после того, как K & R C появилась вживую (около 1978 года). В классическом C для написания надежных программ добавление тестов NULL-указателей и проверка границ массивов необходимо выполнять гораздо чаще, чем в C ++. Можно избежать грубых тестов указателей NULL, используя ссылки и / или умные указатели вместо указателей, а также используяstd::vector
проверка границ массивов часто не требуется. Более того, хит производительности в 1980 году был определенно гораздо важнее, чем сегодня. Поэтому я думаю, что именно по этой причине «assert» был разработан, чтобы быть активным только в отладочных сборках по умолчанию.Более того, для реальной обработки ошибок в производственном коде функция, которая просто проверяет некоторое условие или инвариант и завершает работу программы, если условие не выполняется, в большинстве случаев недостаточно гибкая. Для отладки это, вероятно, нормально, поскольку тот, кто запускает программу и наблюдает за ошибкой, обычно имеет под рукой отладчик для анализа того, что происходит. Однако для производственного кода разумное решение должно быть функцией или механизмом, который
проверяет некоторое условие (и останавливает выполнение в области, где условие не выполняется)
выдает четкое сообщение об ошибке, если условие не выполняется
позволяет внешней области принимать сообщение об ошибке и выводить его на определенный канал связи. Этот канал может быть чем-то вроде stderr, стандартным файлом журнала, окном сообщения в программе с графическим интерфейсом, общим обратным вызовом обработки ошибок, сетевым каналом ошибок или любым другим, наиболее подходящим для конкретной части программного обеспечения.
позволяет внешней области действия для каждого конкретного случая решать, должна ли программа завершиться изящно или должна продолжаться.
(Конечно, бывают ситуации, когда немедленное завершение программы в случае невыполнения условия является единственно разумным вариантом, но в таких случаях это должно происходить и в сборке релиза, а не только в сборке отладки).
Поскольку классика
assert
не предоставляет эти функции, она не подходит для сборки релиза, если предположить, что сборка релиза - это то, что каждый развертывает в производство.Теперь вы можете спросить, почему в стандартной библиотеке C нет такой функции или механизма, который обеспечивает такую гибкость. На самом деле, в C ++ есть стандартный механизм, который имеет все эти функции (и даже больше), и вы это знаете: это называется исключениями .
Однако в C трудно реализовать хороший стандартный механизм общего назначения для обработки ошибок со всеми упомянутыми функциями из-за отсутствия исключений в качестве части языка программирования. Таким образом, большинство программ на Си имеют свои собственные механизмы обработки ошибок с кодами возврата, или «переходами», или «длинными прыжками», или их комбинацией. Это часто прагматичные решения, которые подходят для конкретного вида программы, но не являются «достаточно универсальными», чтобы вписаться в стандартную библиотеку C.
источник
#include <cassert>
). Это полностью имеет смысл сейчас.assert
является неправильным инструментом (исключение может действительно быть и неправильной реакцией). ). Тем не менее, я уверен, что на самом делеassert
также использовался для многих тестов в C, где в C ++ сегодня будут использоваться исключения.Если вы обнаружите, что хотите, чтобы утверждения были включены в выпуске, вы попросили, чтобы утверждения выполняли неправильную работу.
Суть в том, что они не включены в выпуске. Это позволяет тестировать инварианты во время разработки с кодом, который в противном случае должен был бы быть скаффолдинговым кодом. Код, который должен быть удален перед выпуском.
Если у вас есть что-то, что, по вашему мнению, должно быть проверено даже во время выпуска, напишите код, который тестирует это.
If throw
Конструкция работает очень хорошо. Если вы хотите сказать что-то отличное от других бросков, просто используйте описательное исключение, которое говорит, что вы хотите сказать.Дело не в том, что вы не можете изменить способ использования утверждений. Дело в том, что это не принесет вам ничего полезного, противоречит ожиданиям и не оставит вам никакого чистого способа сделать то, что должны были сделать утверждения. Добавьте тесты, которые неактивны в выпуске.
Почему нет relayse_assert? Честно говоря, потому что утверждения не достаточно хороши для производства. Да, есть необходимость, но ничто не удовлетворяет эту потребность хорошо. О, конечно, вы можете создать свой собственный. Механически вашей функции throwIf просто нужно использовать bool и исключение. И это может соответствовать вашим потребностям. Но вы действительно ограничиваете дизайн. Вот почему меня не удивляет то, что в вашей языковой библиотеке нет системы, генерирующей исключения типа assert. Конечно, это не значит, что вы не можете этого сделать. Другие имеют . Но для случая, когда что-то идет не так, для большинства программ приходится 80% работы. И до сих пор никто не показал нам хороший размер, подходящий для всех решений. Эффективно справиться с этими случаями может стать сложным, Если бы у нас была консервированная система release_assert, которая не соответствовала нашим потребностям, я думаю, что она принесла бы больше вреда, чем пользы. Вы просите хорошую абстракцию, которая бы означала, что вам не придется думать об этой проблеме. Я тоже хочу один, но, похоже, мы еще там.
Почему утверждения отключены в выпуске? Утверждения были созданы в разгар возраста кода лесов. Код, который мы должны были удалить, потому что знал, что мы не хотим, чтобы он был в производстве, но знал, что мы хотим запустить в разработке, чтобы помочь нам находить ошибки. Утверждения были более чистой альтернативой
if (DEBUG)
шаблону, который позволял нам оставлять код, но отключать его. Это было до того, как модульное тестирование стало основным способом отделения кода тестирования от производственного кода. Утверждения по-прежнему используются сегодня даже опытными юнит-тестерами, чтобы прояснить ожидания и покрыть случаи, когда они все же лучше, чем юнит-тестирование.Почему бы просто не оставить отладочный код в производстве? Поскольку производственный код не должен смущать компанию, не форматировать жесткий диск, не портить базу данных и не отправлять президенту электронные письма с угрозами. Короче говоря, приятно иметь возможность писать код отладки в безопасном месте, где вам не нужно об этом беспокоиться.
источник
catch(...)
это сделать.if (DEBUG)
для управления чем-то другим, кроме отладки кода. Микро оптимизация может ничего не значить в вашем случае. Но идиома не должна быть подорвана только потому, что она вам не нужна.assert
а о концепции утверждения. Я не хочу злоупотреблятьassert
или путать читателей. Я хотел спросить, почему это так во-первых. Почему нет дополнительногоrelease_assert
? В этом нет необходимости? В чем причина того, что assert отключен в релизе?You're asking for a good abstraction.
Я не уверен в этом. Я в основном хочу иметь дело с проблемами, когда нет восстановления, и это никогда не должно происходить. Возьмите это вместе с,Because production code needs to [...] not format the hard drive [...]
и я бы сказал, что для случаев, когда инварианты нарушены, я бы взял утверждение в выпуске через UB в любое время.Утверждения инструментом отладки , а не защитной техникой программирования. Если вы хотите выполнить проверку при любых обстоятельствах, затем выполните проверку, написав условное выражение - или создайте свой собственный макрос, чтобы уменьшить шаблон.
источник
assert
это форма документации, как комментарии. Как и комментарии, вы обычно не отправляете их клиентам - они не входят в код выпуска.Но проблема с комментариями состоит в том, что они могут устареть, и все же они остаются. Поэтому утверждения хороши - они проверяются в режиме отладки. Когда утверждение устареет, вы быстро его обнаружите и все равно будете знать, как исправить утверждение. Тот комментарий, который устарел 3 года назад? Чье-то предположение.
источник
Если вы не хотите, чтобы «утверждение» было отключено, напишите простую функцию, которая имеет аналогичный эффект:
То есть
assert
для испытаний, когда вы хотите, чтобы они ушли в поставляемом продукте. Если вы хотите, чтобы этот тест был частью определенного поведения программы,assert
это неправильный инструмент.источник
Бессмысленно спорить, что должен делать assert, он делает то, что делает. Если вы хотите что-то другое, напишите свою собственную функцию. Например, у меня есть Assert, который останавливается в отладчике или ничего не делает, у меня есть AssertFatal, который вызывает сбой приложения, у меня есть функции bool Asserted и AssertionFailed, которые утверждают и возвращают результат, так что я могу как утверждать, так и обрабатывать ситуацию.
Для любой непредвиденной проблемы вам нужно решить, как лучше всего ее решить как разработчику, так и пользователю.
источник
verify(cond)
будет ли нормальный способ утверждать и возвращать результат?Как указывали другие,
assert
это своего рода последний оплот защиты от ошибок программистов, которые никогда не должны происходить. Это проверки работоспособности, которые, мы надеемся, не потерпят неудачу влево и вправо к моменту отправки.Он также предназначен для исключения из стабильных сборок релиза по любым причинам, которые могут оказаться полезными для разработчиков: эстетика, производительность, все, что они захотят. Это часть того, что отделяет отладочную сборку от сборки выпуска, и по определению сборка выпуска лишена таких утверждений. Таким образом, существует подрыв дизайна, если вы хотите выпустить аналогичную «сборку релиза с утверждениями на месте», которая была бы попыткой сборки релиза с определением
_DEBUG
препроцессора и безNDEBUG
определения; это больше не релизная версия.Дизайн распространяется даже на стандартную библиотеку. В качестве очень простого примера среди множества других реализаций
std::vector::operator[]
будетassert
проверка работоспособности, чтобы убедиться, что вы не проверяете вектор вне границ. И стандартная библиотека начнет работать намного, намного хуже, если вы включите такие проверки в сборке релиза. Ориентирvector
использованияoperator[]
и заполнитель ctor с такими утверждениями, включенными в простой старый динамический массив, будет часто показывать, что динамический массив работает значительно быстрее, пока вы не отключите такие проверки, поэтому они часто влияют на производительность далеко, далеко не тривиальными способами. Проверка нулевого указателя здесь и проверка вне границ могут действительно стать огромными расходами, если такие проверки применяются миллионы раз для каждого кадра в критических циклах, предшествующих коду, так же просто, как разыменование умного указателя или доступ к массиву.Таким образом, вы, скорее всего, нуждаетесь в другом инструменте для работы, который не предназначен для исключения из сборок релизов, если вы хотите сборки релизов, которые выполняют такие проверки работоспособности в ключевых областях. Самым полезным, что я лично нахожу, является логирование. В этом случае, когда пользователь сообщает об ошибке, все становится намного проще, если он прикрепляет журнал, а последняя строка журнала дает мне большое представление о том, где произошла ошибка и что это может быть. Затем, после воспроизведения их шагов в отладочной сборке, я мог бы также получить ошибку подтверждения, и эта ошибка подтверждения дополнительно дает мне огромные ключи для оптимизации моего времени. Тем не менее, поскольку ведение журнала является относительно дорогим, я не использую его для применения проверок работоспособности на очень низком уровне, таких как проверка доступа к массиву вне границ в общей структуре данных.
И все же, наконец, и в некоторой степени с вами согласился, я мог видеть разумный случай, когда вы могли бы захотеть передать тестерам что-то похожее на отладочную сборку во время альфа-тестирования, например, с небольшой группой альфа-тестеров, которые, скажем, подписали NDA , Там это может упростить альфа-тестирование, если вы дадите своим тестировщикам нечто иное, чем полная сборка релиза, с некоторой отладочной информацией, прикрепленной к некоторым функциям отладки / разработки, таким как тесты, которые они могут выполнить, и более подробный вывод во время работы программного обеспечения. Я, по крайней мере, видел, как некоторые крупные игровые компании делают такие вещи для альфы. Но это для чего-то вроде альфа-тестирования или внутреннего тестирования, когда вы искренне пытаетесь дать тестерам нечто иное, чем сборка релиза. Если вы на самом деле пытаетесь отправить релизную сборку, то по определению
_DEBUG
определяется, иначе это действительно сбивает с толку разницу между сборкой «отладка» и «выпуск».Как указано выше, проверки не обязательно являются тривиальными с точки зрения производительности. Многие из них, вероятно, тривиальны, но опять же, даже стандартная библиотека использует их, и это может повлиять на производительность неприемлемым образом для многих людей во многих случаях, если, скажем, обход произвольного доступа
std::vector
занял в 4 раза больше времени в том, что должно быть оптимизированной сборкой выпуска из-за его проверки границ, которая никогда не должна потерпеть неудачу.В предыдущей команде нам фактически пришлось сделать так, чтобы наша библиотека матриц и векторов исключала некоторые утверждения в определенных критических путях, просто чтобы ускорить отладочные сборки, потому что эти утверждения замедляли математические операции более чем на порядок до точки, где это было Начинаем требовать от нас подождать 15 минут, прежде чем мы сможем даже найти интересующий нас код. Мои коллеги на самом деле хотели просто удалить
asserts
прямо, потому что они обнаружили, что просто делать это имело колоссальную разницу. Вместо этого мы остановились на том, чтобы заставить критические пути отладки избегать их. Когда мы заставили эти критические пути напрямую использовать векторные / матричные данные, не проходя проверку границ, время, необходимое для выполнения полной операции (которая включала в себя не только векторную / матричную математику), сократилось с минут до секунд. Так что это крайний случай, но определенно утверждения не всегда незначительны с точки зрения производительности, даже близко.Но также это просто способ
asserts
, которым разработаны. Если они не оказали такого огромного влияния на производительность по всем направлениям, то я мог бы предпочесть его, если бы они были спроектированы как нечто большее, чем функция отладочной сборки, или мы могли бы использовать,vector::at
который включает проверку границ даже в сборках релизов и выбросы за пределы доступ, например (пока с огромным ударом по производительности). Но в настоящее время я нахожу их дизайн намного более полезным, учитывая их огромное влияние на производительность в моих случаях, как функцию только для отладочной сборки, которая не указывается приNDEBUG
определении. По крайней мере, для случаев, с которыми я работал, сборка релиза имеет огромное значение для исключения проверок работоспособности, которые никогда не должны были вообще сбоить.vector::at
противvector::operator[]
Я думаю, что различие этих двух методов лежит в основе этого, а также альтернативы: исключения.
vector::operator[]
Реализации, как правило,assert
гарантируют, что доступ за пределами границ вызовет легко воспроизводимую ошибку при попытке доступа к вектору за пределами границ. Но разработчики библиотеки делают это с предположением, что в оптимизированной сборке релиза это не будет стоить ни копейки.В то же время
vector::at
предоставляется, который всегда проверяет и выходит за границы даже в сборках релиза, но это снижает производительность до такой степени, что я часто вижу гораздо больше кода,vector::operator[]
чем используюvector::at
. Большая часть дизайна C ++ перекликается с идеей «плати за то, что ты используешь / нуждаешься», и многие люди часто одобряютoperator[]
это, даже не заботясь о проверке границ в сборках релизов, основываясь на том, что они надевают не нужно проверять границы в их оптимизированных сборках. Внезапно, если утверждения были включены в сборках выпуска, производительность этих двух была бы идентична, и использование вектора всегда заканчивалось бы медленнее, чем динамический массив. Таким образом, огромная часть дизайна и преимуществ утверждений основана на идее, что они становятся свободными в сборке релиза.release_assert
Это интересно после обнаружения этих намерений. Естественно, у всех будут разные варианты использования, но я думаю, что я нашел бы какое-то применение для,
release_assert
который выполняет проверку и вылетает программное обеспечение, показывая номер строки и сообщение об ошибке, даже в сборках выпуска.Это было бы для некоторых неясных случаев в моем случае, когда я не хочу, чтобы программное обеспечение корректно восстанавливалось, как если бы было выброшено исключение. Я бы хотел, чтобы в таких случаях он приводил к сбою даже при выпуске, чтобы пользователю можно было дать номер строки, чтобы сообщать, когда программное обеспечение обнаруживает что-то, что никогда не должно происходить, все еще в сфере проверок работоспособности на наличие ошибок программиста, а не внешних ошибок ввода, таких как исключения, но достаточно дешевые, чтобы сделать это, не беспокоясь о его стоимости в выпуске.
На самом деле есть некоторые случаи, когда я обнаружил бы серьезный сбой с номером строки и сообщением об ошибке, предпочтительнее изящного восстановления после выброшенного исключения, которое может быть достаточно дешевым, чтобы его можно было сохранить в выпуске. И в некоторых случаях невозможно выполнить восстановление из исключения, например ошибка, возникшая при попытке восстановления из существующего. Там я бы нашел идеальную подгонку для
release_assert(!"This should never, ever happen! The software failed to fail!");
и, естественно, это было бы очень дешево, так как проверка была бы выполнена внутри исключительного пути в первую очередь и не стоила бы ничего в обычных путях выполнения.источник
Additionally, the performance argument for me only counts when it is a measurable problem.
так что идея состоит в том, чтобы в каждом конкретном случае решать, когда вынести утверждение из релиза.assert
которое использует те же определения_DEBUG/NDEBUG
препроцессора, что и при использованииassert
макроса. Таким образом, нет способа выборочно включить утверждения только для одного фрагмента кода, не включая его и для стандартной библиотеки, например, предполагая, что она использует стандартную библиотеку.assert
.vector::operator[]
иvector::at
, например, он имел бы практически одинаковую производительность, если бы утверждения были включены в сборках релизов, и не было бы никакого смыслаoperator[]
больше использовать с точки зрения производительности, так как он все равно будет выполнять проверку границ.Я пишу на Ruby, а не на C / C ++, и поэтому я не буду говорить о разнице между утверждениями и исключениями, но я хотел бы поговорить об этом как о том, что останавливает среду выполнения . Я не согласен с большинством ответов, приведенных выше, поскольку остановка программы с печатью обратной трассировки прекрасно работает для меня как метод защитного программирования.
Если есть способ для подпрограммы assert (абсолютно независимо от того, как она синтаксически написана, и если слово «assert» используется или существует в языке программирования или вообще в dsl), то это означает, что некоторая работа должна быть выполнена и продукт немедленно возвращается от «выпущенного, готового к использованию» к «нужному исправлению» - теперь либо переписайте его для реальной обработки исключений, либо исправьте ошибку, из-за которой появлялись неправильные данные.
Я имею в виду, что Assert - это не то, с чем вам следует жить и часто звонить - это сигнал остановки, который указывает, что вы должны сделать что-то, чтобы это никогда не повторилось. И говорить, что «релизная сборка не должна иметь утверждений», все равно, что говорить «релизная сборка не должна иметь ошибок» - чувак, это почти невозможно, разберись с этим.
Или думайте о них как о «провальных юнит-тестах, выполненных и запущенных конечным пользователем». Вы не можете предсказать все, что пользователь сделает с вашей программой, но если что-то слишком серьезное пойдет не так, это должно прекратиться - это похоже на то, как вы строите конвейеры сборки - вы останавливаете процесс и не публикуете, не так ли? ? Утверждение заставляет пользователя останавливаться, сообщать и ждать вашей помощи.
источник