Использует assert() в плохой практике С++?

Я обычно добавляю множество утверждений в свой код на С++, чтобы упростить отладку, не влияя на производительность выпусков. Теперь assert - это чистый макрос C, созданный без использования механизмов С++.

C++, с другой стороны, определяет std::logic_error, который должен быть брошен в случаях, когда в логике программы есть ошибка (отсюда и название). Выброс экземпляра может быть просто идеальной, более альтернативной версии С++ для assert.

Проблема заключается в том, что assert и abort обе завершают программу немедленно, не вызывая деструкторы, поэтому пропускают очистку, в то время как выброс исключения вручную добавляет ненужные затраты времени выполнения. Один из способов создания собственного макроса подтверждения SAFE_ASSERT, который работает так же, как и C-копия, но генерирует исключение при ошибке.

Я могу представить три мнения по этой проблеме:

  • Придерживайтесь утверждения C. Поскольку программа немедленно прекращается, не имеет значения, правильно ли развернуты изменения. Кроме того, использование #define в С++ так же плохо.
  • Выбросьте исключение и поймайте его в main(). Предоставление кода для пропуска деструкторов в любом состоянии программы является плохой практикой, и его следует избегать любой ценой, а также вызовы terminate(). Если выбрасываются исключения, они должны быть пойманы.
  • Выбросить исключение и завершить его. Исключение, заканчивающееся программой, в порядке, и из-за NDEBUG этого никогда не произойдет в сборке релиза. Ловушка не нужна и предоставляет детали реализации внутреннего кода main().

Есть ли окончательный ответ на эту проблему? Любая профессиональная ссылка?

Отредактировано: Пропуск деструкторов - это, конечно, поведение undefined.

Ответ 1

Утверждения полностью подходят для кода на С++. Исключения и другие механизмы обработки ошибок на самом деле не предназначены для утверждений.

Обработка ошибок - это когда есть возможность правильно восстановить или сообщить о проблеме пользователю. Например, если есть ошибка при попытке прочитать входной файл, вы можете что-то сделать с этим. Ошибки могут возникать из-за ошибок, но они также могут быть просто соответствующим выходом для данного ввода.

Утверждения предназначены для таких вещей, как проверка соответствия требований API, когда API обычно не проверяется, или для проверки того, что разработчик полагает, что он гарантирует строительство. Например, если алгоритм требует отсортированного ввода, вы обычно не проверяли бы это, но у вас может быть утверждение, чтобы проверить его, чтобы отладка создавала флаг такой ошибки. В утверждении всегда указывается некорректно работающая программа.


Если вы пишете программу, где нечистое завершение работы может вызвать проблему, вы можете избежать утверждений. Undefined поведение строго в терминах языка С++ не может рассматриваться как проблема здесь, так как удар по утверждению, вероятно, уже является результатом поведения Undefined или нарушения какого-либо другого требования, которое могло бы предотвратить некоторую очистку от правильной работы.

Также, если вы реализуете утверждения в терминах исключения, его можно потенциально поймать и обработать, даже если это противоречит самой цели утверждения.

Ответ 2

  • Утверждения предназначены для отладки. Пользователь вашего отправленного кода никогда не должен их видеть. Если утверждение было удалено, ваш код должен быть исправлен.

  • Исключения для исключительных обстоятельств. Если кто-то встречается, пользователь не сможет делать то, что хочет, но может вернуться в другое место.

  • Обработка ошибок - это обычный поток программы. Например, если вы запрашиваете у пользователя номер и получаете что-то непоправимое, это нормально, потому что пользовательский ввод не находится под вашим контролем, и вы должны всегда обрабатывать все возможные ситуации, как само собой разумеющееся. (Например, цикл до тех пор, пока у вас не будет действительного ввода, после чего "Извините, попробуйте еще раз".)

Ответ 3

Не запускать деструкторы из-за alling abort() не является неопределенным поведением!

Если бы это было так, то было бы неопределенным поведением вызывать также std::terminate(), и какой смысл предоставлять его?

assert() так же полезен в C++, как и в C. Утверждения не для обработки ошибок, а для немедленной отмены программы.

Ответ 4

Утверждения могут использоваться для проверки внутренних инвариантов реализации, таких как внутреннее состояние до или после выполнения какого-либо метода и т.д. Если утверждение не выполняется, это действительно означает, что логика программы нарушена, и вы не можете оправиться от этого. В этом случае лучшее, что вы можете сделать, - это как можно скорее сломаться, не передавая исключение пользователю. Что действительно приятно в отношении утверждений (по крайней мере, в Linux), так это то, что основной дамп генерируется в результате завершения процесса, и поэтому вы можете легко исследовать трассировку стека и переменные. Это гораздо более полезно для понимания логического сбоя, чем сообщение об исключении.

Ответ 5

ИМХО, утверждения предназначены для проверки условий, которые, если они нарушаются, делают все остальное бессмысленным. И поэтому вы не можете оправиться от них или, вернее, восстановление не имеет значения.

Я бы сгруппировал их по двум категориям:

  • Грех разработчика (например, функция вероятности, возвращающая отрицательные значения):

float вероятность() {return -1.0; }

assert (вероятность() > 0.0)

  • Машина разбита (например, машина, которая запускает вашу программу, очень не правильная):

int x = 1;

assert (x > 0);

Это и тривиальные примеры, но не слишком далекие от реальности. Например, подумайте о наивных алгоритмах, которые возвращают отрицательные индексы для использования с векторами. Или встроенные программы в настраиваемое оборудование. Вернее, потому что дерьмо бывает.

И если есть такие ошибки в разработке, вы не должны быть уверены в том, что какой-либо механизм восстановления или обработки ошибок реализован. То же самое касается аппаратных ошибок.