W C można „symulować” wyjątki wraz z automatycznym „odzyskiwaniem obiektów” poprzez ręczne użycie if + goto do jawnej obsługi błędów.
Często piszę kod w C, jak poniżej (sprowadzając się do obsługi błędów):
#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;
}
Jest to całkowicie standardowe ANSI C, oddziela obsługę błędów od głównego kodu, umożliwia (ręczne) rozwijanie stosu zainicjowanych obiektów, podobnie jak C ++, i jest całkowicie oczywiste, co się tutaj dzieje. Ponieważ w każdym momencie jawnie testujesz błąd, ułatwia to wstawianie określonego rejestrowania lub obsługi błędów w każdym miejscu, w którym może wystąpić błąd.
Jeśli nie masz nic przeciwko odrobinie magii makr, możesz uczynić to bardziej zwięzłym, wykonując inne czynności, takie jak rejestrowanie błędów ze śladami stosu. Na przykład:
#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;
}
Oczywiście nie jest to tak eleganckie, jak wyjątki C ++ + destruktory. Na przykład zagnieżdżanie wielu stosów obsługi błędów w jednej funkcji w ten sposób nie jest zbyt czyste. Zamiast tego prawdopodobnie chciałbyś podzielić je na niezależne funkcje podrzędne, które podobnie obsługują błędy, inicjalizuj + finalizuj jawnie w ten sposób.
Działa to również tylko w ramach pojedynczej funkcji i nie będzie przeskakiwać w górę stosu, chyba że wywołania wyższego poziomu zaimplementują podobną jawną logikę obsługi błędów, podczas gdy wyjątek C ++ będzie po prostu przeskakiwał stos, dopóki nie znajdzie odpowiedniego programu obsługi. Nie pozwala też na wyrzucenie dowolnego typu, ale zamiast tego tylko kod błędu.
Systematyczne kodowanie w ten sposób (tj. - z pojedynczym wejściem i pojedynczym punktem wyjścia) również bardzo ułatwia wstawianie logiki pre i post („ostatecznie”), która będzie wykonywana bez względu na wszystko. Po etykiecie END umieszczasz swoją logikę „ostatecznie”.