Di C, Anda dapat "mensimulasikan" pengecualian bersama dengan "reklamasi objek" otomatis melalui penggunaan manual if + goto untuk penanganan error eksplisit.
Saya sering menulis kode C seperti berikut (diringkas untuk menyoroti penanganan kesalahan):
#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;
}
Ini sepenuhnya ANSI C standar, memisahkan penanganan kesalahan dari kode jalur utama Anda, memungkinkan pelepasan tumpukan (manual) objek yang diinisialisasi seperti yang dilakukan C ++, dan sangat jelas apa yang terjadi di sini. Karena Anda secara eksplisit menguji kegagalan di setiap titik, itu membuatnya lebih mudah untuk memasukkan logging tertentu atau penanganan kesalahan di setiap tempat kesalahan dapat terjadi.
Jika Anda tidak keberatan dengan sedikit keajaiban makro, maka Anda dapat membuatnya lebih ringkas sambil melakukan hal-hal lain seperti mencatat kesalahan dengan jejak tumpukan. Sebagai contoh:
#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;
}
Tentu saja, ini tidak seanggun C ++ exception + destructors. Misalnya, menumpuk beberapa tumpukan penanganan kesalahan dalam satu fungsi dengan cara ini tidak terlalu bersih. Sebaliknya, Anda mungkin ingin memecahnya menjadi sub fungsi mandiri yang menangani kesalahan dengan cara yang sama, menginisialisasi + menyelesaikan secara eksplisit seperti ini.
Ini juga hanya berfungsi dalam satu fungsi dan tidak akan terus meningkatkan tumpukan kecuali pemanggil tingkat yang lebih tinggi menerapkan logika penanganan kesalahan eksplisit yang serupa, sedangkan pengecualian C ++ hanya akan terus meningkatkan tumpukan hingga menemukan penangan yang sesuai. Itu juga tidak memungkinkan Anda untuk melempar tipe arbitrer, tetapi hanya kode kesalahan.
Pengodean secara sistematis dengan cara ini (yaitu - dengan satu entri dan satu titik keluar) juga membuatnya sangat mudah untuk memasukkan logika pra dan posting ("akhirnya") yang akan dijalankan apa pun yang terjadi. Anda hanya menempatkan logika "akhirnya" Anda setelah label AKHIR.