Является использование assert() в C++ плохой практикой?
Я обычно добавляю много утверждений в свой код C++, чтобы упростить отладку, не влияя на производительность сборок выпуска. Теперь,assert Это чистый макрос C, разработанный без механизмов C++ в виду.
в C++ с другой стороны определяет std::logic_error, который должен быть брошен в тех случаях, когда есть ошибка в логике программы (отсюда и название). Бросание экземпляра может быть просто идеальной, более C++ish альтернативой assert.
проблема в том, что assert и abort оба завершают программу немедленно без вызова деструкторов, поэтому пропускают очистку, в то время как исключение вручную добавляет ненужные затраты времени выполнения. Один из способов обойти это будет создание собственного утверждения макроса SAFE_ASSERT, который работает так же, как и C-аналог, но выдает исключение при сбое.
я могу придумать три мнения по этой проблеме:
придерживайтесь утверждения C. С тех пор программа немедленно завершается, не имеет значения, правильно ли развернуты изменения. Кроме того, используя#defines в C++ так же плохо.
бросьте исключение и поймайте его в main (). Разрешение кода пропускать деструкторы в любом состоянии программы является плохой практикой и должно быть избегнуто любой ценой, а также вызовы terminate(). Если возникают исключения, они должны быть перехвачены.
бросьте исключение и пусть оно завершит программа. исключение, завершающее программу, в порядке, и из-заNDEBUG, это никогда не произойдет в сборке выпуска. Перехват является ненужным и предоставляет детали реализации внутреннего кода дляmain().
есть ли окончательный ответ на эту проблему? Любая профессиональная ссылка?
редактировать: пропуск деструкторов, конечно, не является неопределенным поведением.
5 ответов:
утверждения полностью уместны в коде C++. Исключения и другие механизмы обработки ошибок на самом деле не предназначены для того же, что и утверждения.
обработка ошибок предназначена для того, когда есть потенциал для восстановления или сообщения об ошибке красиво пользователю. Например, если есть ошибка при попытке прочитать входной файл, вы можете сделать что-то об этом. Ошибки могут быть результатом ошибок, но они также могут быть просто подходящим выходом для данного вход.
утверждения предназначены для таких вещей, как проверка соответствия требованиям API, когда API обычно не проверяется, или для проверки того, что разработчик считает, что он гарантирован конструкцией. Например, если алгоритм требует отсортированного ввода, вы обычно не проверяете это, но у вас может быть утверждение, чтобы проверить его, чтобы отладка строила флаг такого рода ошибки. Утверждение всегда должно указывать на неверно работающую программу.
Если вы написание программы, где нечистое завершение работы может вызвать проблему, то вы можете избежать утверждений. Неопределенное поведение строго с точки зрения языка C++ здесь не квалифицируется как такая проблема, поскольку попадание в утверждение, вероятно, уже является результатом неопределенного поведения или нарушения какого-либо другого требования, которое может помешать некоторой очистке работать должным образом.
кроме того, если вы реализуете утверждения в терминах исключения, то он потенциально может быть пойман и "обработано", хотя это противоречит самой цели утверждения.
утверждения для отладка. Пользователь вашего отправленного кода никогда не должен их видеть. Если утверждение попадает, ваш код должен быть исправлен.
исключение исключительных обстоятельств. Если один из них встречается, пользователь не сможет делать то, что он хочет, но может быть в состоянии возобновить где-то еще.
обработка ошибок для нормального потока программы. Например, если вы запросите у пользователя число и получить что-то непростительное, это нормальный, потому что пользовательский ввод не находится под вашим контролем, и вы всегда должны обрабатывать все возможные ситуации, как само собой разумеющееся. (Например, цикл, пока у вас есть действительный вход, говоря "извините, попробуйте еще раз" между ними.)
утверждения могут использоваться для проверки внутренних инвариантов реализации, таких как внутреннее состояние до или после выполнения некоторого метода и т. д. Если утверждение терпит неудачу, это действительно означает, что логика программы нарушена, и вы не можете оправиться от этого. В этом случае лучшее, что вы можете сделать, это сломать как можно скорее без передачи исключения пользователю. Что действительно приятно в утверждениях (по крайней мере, в Linux), так это то, что дамп ядра генерируется в результате завершения процесса, и поэтому вы можете легко исследовать трассировку стека и переменные. Это гораздо полезнее для понимания логического сбоя, чем сообщение об исключении.
не запуск деструкторов из-за alling abort() не является неопределенным поведением!
Если бы это было, то это было бы неопределенное поведение для вызова
std::terminate()тоже, и так какой смысл в его предоставлении?
assert()так же полезно в C++, как и в C. утверждения не предназначены для обработки ошибок, они предназначены для немедленного прерывания программы.
ИМХО, утверждения предназначены для проверки условий, которые при нарушении делают все остальное ерундой. И поэтому вы не можете оправиться от них или, скорее, восстановление не имеет значения.
Я бы сгруппировал их в 2 категории:
- разработчик грехи (например, функция вероятности, которая возвращает отрицательные значения ):
float probability () { return -1.0;}
утверждать(вероятности() > 0.0)
- машина сломана (например, машина, которая запускает вашу программу очень неправильно):
int x = 1;
assert(x > 0);
Это как тривиальные примеры, но не слишком далеко от реальности. Например, думать об алгоритмах, которые возвращают отрицательные индексы для использования с векторами. Или встроенные программы в пользовательском оборудовании. Или скорее потому что говно бывает.
и если есть такие ошибки в разработке, вы не должны быть уверены в каком-либо механизме восстановления или обработки ошибок. То же самое относится и к аппаратным ошибкам.
Comments