Неправильно ли переопределить макрос утверждения?Зло ли переопределить утверждение?
Некоторые люди рекомендуют использовать собственный макрос ASSERT (cond), а не переопределять существующий стандартный, assert (cond) макрос. Но это не помогает, если у вас много устаревшего кода, использующего assert(), который вы не хотите вносить изменения в исходный код, который вы хотите перехватить, упорядочить, сообщать об утверждении.
Я сделал
#undef assert
#define assert(cond) ... my own assert code ...
в таких ситуациях, как выше - код уже использует утверждать, что я хотел бы расширить Assert-провал поведение - когда я хотел сделать такие вещи, как
1) печать дополнительной информации об ошибке, чтобы сделать утверждение более полезными
2) автоматически, вызывая отладчик или стеку дорожку на утверждают
.. это, 2), может быть сделано без переопределения assert путем реализации обработчика сигнала SIGABRT.
3) преобразование ошибок утверждения в броски.
... это, 3), не может быть выполнено обработчиком сигнала - поскольку вы не можете исключить исключение C++ из обработчика сигнала. (По крайней мере, не надежно.)
Почему я могу сделать assert throw? Сложная обработка ошибок.
Я делаю это последнее обычно не потому, что хочу, чтобы программа продолжала работать после утверждения (хотя см. Ниже), а потому, что мне нравится использовать исключения, чтобы обеспечить лучший контекст ошибок. Я часто:
int main() {
try { some_code(); }
catch(...) {
std::string err = "exception caught in command foo";
std::cerr << err;
exit(1);;
}
}
void some_code() {
try { some_other_code(); }
catch(...) {
std::string err = "exception caught when trying to set up directories";
std::cerr << err;
throw "unhandled exception, throwing to add more context";
}
}
void some_other_code() {
try { some_other2_code(); }
catch(...) {
std::string err = "exception caught when trying to open log file " + logfilename;
std::cerr << err;
throw "unhandled exception, throwing to add more context";
}
}
т.д.
Т.е. обработчики исключений добавляют немного больше контекста ошибки, а затем ретронизуют.
Иногда у меня есть обработчики обработчиков исключений, например. к stderr.
Иногда у меня есть обработчики исключений, которые нажимают на стек сообщений об ошибках. (Очевидно, что не будет работать, когда проблема уходит из памяти.)
** Эти утверждают исключения до сих пор выйти ... **
кто-нибудь комментировал этот пост, @IanGoldby, сказал " Идея утверждения, который не выходит, не имеет для меня никакого смысла ».
Lest I was not clear: Обычно у меня есть такие исключения. Но в конце концов, возможно, не сразу.
E.g.вместо
#include <iostream>
#include <assert.h>
#define OS_CYGWIN 1
void baz(int n)
{
#if OS_CYGWIN
assert(n == 1 && "I don't know how to do baz(1) on Cygwin). Should not call baz(1) on Cygwin.");
#else
std::cout << "I know how to do baz(n) most places, and baz(n), n!=1 on Cygwin, but not baz(1) on Cygwin.\n";
#endif
}
void bar(int n)
{
baz(n);
}
void foo(int n)
{
bar(n);
}
int main(int argc, char** argv)
{
foo(argv[0] == std::string("1"));
}
производить только
% ./assert-exceptions
assertion "n == 1 && "I don't know how to do baz(1) on Cygwin). Should not call baz(1) on Cygwin."" failed: file "assert-exceptions.cpp", line 9, function: void baz(int)
/bin/sh: line 1: 22180 Aborted (core dumped) ./assert-exceptions/
%
вы могли бы сделать
#include <iostream>
//#include <assert.h>
#define assert_error_report_helper(cond) "assertion failed: " #cond
#define assert(cond) {if(!(cond)) { std::cerr << assert_error_report_helper(cond) "\n"; throw assert_error_report_helper(cond); } }
//^ TBD: yes, I know assert needs more stuff to match the definition: void, etc.
#define OS_CYGWIN 1
void baz(int n)
{
#if OS_CYGWIN
assert(n == 1 && "I don't know how to do baz(1) on Cygwin). Should not call baz(1) on Cygwin.");
#else
std::cout << "I know how to do baz(n) most places, and baz(n), n!=1 on Cygwin, but not baz(1) on Cygwin.\n";
#endif
}
void bar(int n)
{
try {
baz(n);
}
catch(...) {
std::cerr << "trying to accomplish bar by baz\n";
throw "bar";
}
}
void foo(int n)
{
bar(n);
}
int secondary_main(int argc, char** argv)
{
foo(argv[0] == std::string("1"));
}
int main(int argc, char** argv)
{
try {
return secondary_main(argc,argv);
}
catch(...) {
std::cerr << "main exiting because of unknown exception ...\n";
}
}
и получить немного более содержательные сообщения об ошибках
assertion failed: n == 1 && "I don't know how to do baz(1) on Cygwin). Should not call baz(1) on Cygwin."
trying to accomplish bar by baz
main exiting because of unknown exception ...
Я не должен объяснить, почему это продолжение ext чувствительные сообщения об ошибках могут быть более значимыми. . пользователь может не иметь ни малейшего представления о том, почему вызывается baz (1). Возможно, это ошибка пограммы - на cygwin вам может потребоваться вызвать cygwin_alternative_to_baz (1).
Но пользователь может понять, что такое «бар».
Да: это не гарантируется. Но, в этом отношении, утверждения не гарантируют работу, если они делают что-то более сложное, чем вызов в обработчике прерывания.
write(2,"error baz(1) has occurred",64);
и даже то, что не гарантирует работу (есть безопасная ошибка в этом вызове.)
Э.Г. если malloc или sbrk не удалось.
Почему я могу сделать запрос assert? Тестирование
Другая большая причина, по которой я иногда переопределял утверждение, заключалась в том, чтобы написать модульные тесты для устаревшего кода, кода, который использует assert для ошибок сигнала, которые мне не разрешено переписывать.
Если этот код является библиотечным кодом, тогда удобно переносить вызовы через try/catch. Проверьте, обнаружена ли ошибка, и продолжайте.
О, черт возьми, я мог бы также признать это: иногда я написал этот код устаревшего. И я сознательно использовал assert() для сигнализации ошибок. Потому что я не мог положиться на пользователя, делающего try/catch/throw - на самом деле, часто один и тот же код должен использоваться в среде C/C++. Я не хотел использовать свой собственный макрос ASSERT, потому что, верьте или нет, ASSERT часто конфликтует. Я нахожу код, который усеян FOOBAR_ASSERT() и A_SPECIAL_ASSERT() уродливым. Нет ... просто использование assert() само по себе является элегантным, работает в основном. И может быть расширен ... если нормально переопределить assert().
В любом случае, является ли код, который использует assert() моим или кем-то другим: иногда вы хотите, чтобы код терпел неудачу, вызывая SIGABRT или exit (1) - и иногда вы хотите, чтобы он выбрасывал.
Я знаю, как проверить код, который не по выходу (а) или SIGABRT - что-то вроде
for all tests do
fork
... run test in child
wait
check exit status
но этот код медленно. Не всегда переносимый. И часто выполняется в несколько тысяч раз медленнее
for all tests do
try {
... run test in child
} catch (...) {
...
}
Это опаснее, чем просто контекстное сообщение об ошибках, поскольку вы можете продолжить работу. Но вы всегда можете выбирать типы исключений для cactch.
Мета-наблюдения
Я с Андреем Alexandresciu, полагая, что исключения являются наиболее известным способом сообщать об ошибках в коде, который хочет быть безопасным. (Потому что программист не может забыть проверить код возврата ошибки.)
Если это правильно ... если есть фаза изменения в сообщении об ошибках, от выхода (1)/сигналов/до исключений ... один по-прежнему есть вопрос о том, как жить с устаревшим кодом.
И, в целом, существует несколько схем отчетности об ошибках. Если разные библиотеки используют разные схемы, как заставить их жить вместе.
Это может быть более подходящим для программистов, поскольку вы ищете консенсус по * культуре программирования *, а не по решению конкретной проблемы программирования. – dmckee
Вы должны определить свой собственный ASSERT. Переопределение чего-то из стандартного технически может привести к неопределенному поведению. Если у вас много старого кода, который необходимо изменить, вам нужен хороший редактор кода или IDE с инструментами рефакторинга. –
Хотя я согласен с тем, что собственное определение может избежать некоторых проблем, я не верю, что переопределение 'assert()' является тем злом, что можно сделать это на единицу компиляции. Я сделал это самостоятельно на определенных платформах, это не обескураживает языковой спецификацией: См. 7.2.1.1 Макрос утверждений в C99 Обоснование 5.10: http://www.open-std.org/jtc1/sc22/ wg14/www/C99RationaleV5.10.pdf – Sam