В C вы можете «имитировать» исключения вместе с автоматическим «восстановлением объекта» посредством ручного использования if + goto для явной обработки ошибок.
Я часто пишу C-код, подобный следующему (сводится к тому, чтобы выделить обработку ошибок):
#include <assert.h>
typedef int errcode;
errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
errcode ret = 0;
if ( ( ret = foo_init( f ) ) )
goto FAIL;
if ( ( ret = goo_init( g ) ) )
goto FAIL_F;
if ( ( ret = poo_init( p ) ) )
goto FAIL_G;
if ( ( ret = loo_init( l ) ) )
goto FAIL_P;
assert( 0 == ret );
goto END;
/* error handling and return */
/* Note that we finalize in opposite order of initialization because we are unwinding a *STACK* of initialized objects */
FAIL_P:
poo_fini( p );
FAIL_G:
goo_fini( g );
FAIL_F:
foo_fini( f );
FAIL:
assert( 0 != ret );
END:
return ret;
}
Это полностью стандартный ANSI C, который отделяет обработку ошибок от вашего основного кода, позволяет (вручную) раскручивать инициализированные объекты, как это делает C ++, и совершенно очевидно, что здесь происходит. Поскольку вы явно проверяете на наличие сбоев в каждой точке, это действительно упрощает добавление определенного журнала или обработки ошибок в каждом месте, где может произойти ошибка.
Если вы не возражаете против небольшой магии макросов, вы можете сделать это более кратким, делая другие вещи, например, записывая ошибки с трассировкой стека. Например:
#include <assert.h>
#include <stdio.h>
#include <string.h>
#define TRY( X, LABEL ) do { if ( ( X ) ) { fprintf( stderr, "%s:%d: Statement '" #X "' failed! %d, %s\n", __FILE__, __LINE__, ret, strerror( ret ) ); goto LABEL; } while ( 0 )
typedef int errcode;
errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
errcode ret = 0;
TRY( ret = foo_init( f ), FAIL );
TRY( ret = goo_init( g ), FAIL_F );
TRY( ret = poo_init( p ), FAIL_G );
TRY( ret = loo_init( l ), FAIL_P );
assert( 0 == ret );
goto END;
/* error handling and return */
FAIL_P:
poo_fini( p );
FAIL_G:
goo_fini( g );
FAIL_F:
foo_fini( f );
FAIL:
assert( 0 != ret );
END:
return ret;
}
Конечно, это не так элегантно, как исключения + деструкторы C ++. Например, вложение нескольких стеков обработки ошибок в одну функцию таким способом не очень удобно. Вместо этого вы, вероятно, захотите разбить их на автономные подфункции, которые аналогичным образом обрабатывают ошибки, инициализировать + завершить явно, как это.
Это также работает только в рамках одной функции и не будет продолжать прыгать вверх по стеку, если вызывающие объекты более высокого уровня не реализуют аналогичную явную логику обработки ошибок, тогда как исключение C ++ будет просто продолжать прыгать вверх по стеку, пока не найдет подходящий обработчик. Также он не позволяет вам генерировать произвольный тип, а вместо этого только код ошибки.
Систематическое кодирование таким образом (т.е. с одной точкой входа и одной точкой выхода) также упрощает вставку логики до и после («наконец»), которая будет выполняться несмотря ни на что. Вы просто помещаете логику "finally" после метки END.