Если я использую assert()
, и утверждение терпит неудачу, то assert()
вызовет abort()
, завершая работу программы внезапно. Я не могу себе это позволить в своем производственном кодексе. Есть ли способ утверждать во время выполнения, но уметь ломать неудачные утверждения, чтобы я мог обработать их изящно?
Как я могу утверждать() без использования функции abort()?
Ответ 1
Да, на самом деле есть. Вам нужно будет написать пользовательскую функцию assert самостоятельно, так как С++ assert()
- это точно C assert()
, с включенной функцией abort()
. К счастью, это удивительно просто.
Assert.hh
template <typename X, typename A>
inline void Assert(A assertion)
{
if( !assertion ) throw X();
}
Вышеуказанная функция генерирует исключение, если предикат не выполняется. Тогда у вас будет возможность поймать исключение. Если вы не поймаете исключение, будет вызван terminate()
, который завершит программу аналогично abort()
.
Вы можете задаться вопросом, как оптимизировать утверждение, когда мы строим для производства. В этом случае вы можете определить константы, которые означают, что вы создаете для производства, а затем ссылаетесь на константу, когда вы assert()
.
debug.hh
#ifdef NDEBUG
const bool CHECK_WRONG = false;
#else
const bool CHECK_WRONG = true;
#endif
main.cc
#include<iostream>
struct Wrong { };
int main()
{
try {
Assert<Wrong>(!CHECK_WRONG || 2 + 2 == 5);
std::cout << "I can go to sleep now.\n";
}
catch( Wrong e ) {
std::cerr << "Someone is wrong on the internet!\n";
}
return 0;
}
Если CHECK_WRONG
является константой, то вызов assert()
будет скомпилирован в процессе производства, даже если утверждение не является постоянным выражением. Есть небольшой недостаток в том, что, обращаясь к CHECK_WRONG
, мы вводим немного больше. Но взамен мы получаем преимущество в том, что мы можем классифицировать различные группы утверждений и включать и отключать каждый из них по своему усмотрению. Так, например, мы могли бы определить группу утверждений, которые мы хотим включить даже в производственный код, а затем определить группу утверждений, которые мы хотим видеть только в сборках разработки.
Функция assert()
эквивалентна типу
if( !assertion ) throw X();
но это ясно указывает на намерение программиста: сделайте утверждение. Утверждения также легче для grep для этого подхода, как и обычные assert()
s.
Подробнее об этом методе см. Bjarne Stroustrup. Язык программирования С++ 3e, раздел 24.3.7.2.
Ответ 2
функции отображения ошибок glib придерживаются подхода, продолжающегося после утверждения. glib - это базовая библиотека независимости платформы, которую использует Gnome (через GTK). Здесь макрос, который проверяет предварительное условие и печатает трассировку стека, если предварительное условие терпит неудачу.
#define RETURN_IF_FAIL(expr) do { \
if (!(expr)) \
{ \
fprintf(stderr, \
"file %s: line %d (%s): precondition `%s' failed.", \
__FILE__, \
__LINE__, \
__PRETTY_FUNCTION__, \
#expr); \
print_stack_trace(2); \
return; \
}; } while(0)
#define RETURN_VAL_IF_FAIL(expr, val) do { \
if (!(expr)) \
{ \
fprintf(stderr, \
"file %s: line %d (%s): precondition `%s' failed.", \
__FILE__, \
__LINE__, \
__PRETTY_FUNCTION__, \
#expr); \
print_stack_trace(2); \
return val; \
}; } while(0)
Здесь функция, которая печатает трассировку стека, написанную для среды, которая использует gnu toolchain (gcc):
void print_stack_trace(int fd)
{
void *array[256];
size_t size;
size = backtrace (array, 256);
backtrace_symbols_fd(array, size, fd);
}
Вот как вы будете использовать макросы:
char *doSomething(char *ptr)
{
RETURN_VAL_IF_FAIL(ptr != NULL, NULL); // same as assert(ptr != NULL), but returns NULL if it fails.
if( ptr != NULL ) // Necessary if you want to define the macro only for debug builds
{
...
}
return ptr;
}
void doSomethingElse(char *ptr)
{
RETURN_IF_FAIL(ptr != NULL);
}
Ответ 3
Утверждения в C/С++ выполняются только в сборках отладки. Так что это не произойдет во время выполнения. В целом утверждения должны отмечать вещи, которые, если они случаются, указывают на ошибку и обычно показывают предположения в вашем коде и т.д.
Если вы хотите иметь код, который проверяет наличие ошибок во время выполнения (в выпуске), вы, вероятно, должны использовать исключения, а не утверждения, поскольку они предназначены для них. Ваш ответ в основном обертывает метаданные исключения в синтаксисе assert. Хотя это будет работать, нет особого преимущества для этого, я вижу, что просто выбрал исключение в первую очередь.
Ответ 4
Вот то, что у меня есть в "assert.h" (Mac OS 10.4):
#define assert(e) ((void) ((e) ? 0 : __assert (#e, __FILE__, __LINE__)))
#define __assert(e, file, line) ((void)printf ("%s:%u: failed assertion `%s'\n", file, line, e), abort(), 0)
Исходя из этого, замените вызов на abort() броском (исключение). И вместо printf вы можете отформатировать строку в сообщении об ошибке исключения. В итоге вы получите что-то вроде этого:
#define assert(e) ((void) ((e) ? 0 : my_assert (#e, __FILE__, __LINE__)))
#define my_assert( e, file, line ) ( throw std::runtime_error(\
std::string(file:)+boost::lexical_cast<std::string>(line)+": failed assertion "+e))
Я не пытался его скомпилировать, но вы поняли смысл.
Примечание. Вам необходимо убедиться, что заголовок "исключение" всегда включен, а также повысить (если вы решите использовать его для форматирования сообщения об ошибке). Но вы также можете сделать функцию "my_assert" и объявить только ее прототип. Что-то вроде:
void my_assert( const char* e, const char* file, int line);
И реализуйте его где-нибудь, где вы можете свободно включать все необходимые вам заголовки.
Оберните его в файл #ifdef DEBUG, если вам это нужно, или нет, если вы всегда хотите выполнить эти проверки.
Ответ 5
Если вы хотите вывести строку символов с информацией об утверждении: http://xll8.codeplex.com/SourceControl/latest#xll/ensure.h
Ответ 6
_set_error_mode(_OUT_TO_MSGBOX);
Поверьте мне, эта функция может вам помочь.