У C ви можете "імітувати" винятки разом з автоматичною "рекультивацією об'єктів" шляхом ручного використання if + goto для явної обробки помилок.
Я часто пишу код С на зразок наступного (зводиться до виділення помилок):
#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 ++ просто продовжує стрибати стек, поки не знайде відповідний обробник. Він також не дозволяє викидати довільний тип, а натомість лише код помилки.
Систематичне кодування таким чином (тобто, з єдиним входом і єдиною точкою виходу) також дозволяє дуже легко вставити перед і після ("нарешті") логіку, яка буде виконуватися незалежно від того. Ви просто ставите свою "остаточну" логіку після мітки END.