Trong C, bạn có thể "mô phỏng" các ngoại lệ cùng với "hoàn nguyên đối tượng" tự động thông qua việc sử dụng thủ công if + goto để xử lý lỗi rõ ràng.
Tôi thường viết mã C như sau (đun sôi để làm nổi bật xử lý lỗi):
#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;
}
Đây hoàn toàn là ANSI C tiêu chuẩn, tách biệt việc xử lý lỗi khỏi mã dòng chính của bạn, cho phép tháo cuộn (thủ công) các đối tượng được khởi tạo giống như C ++ và hoàn toàn rõ ràng những gì đang xảy ra ở đây. Bởi vì bạn đang kiểm tra lỗi một cách rõ ràng tại mỗi thời điểm, điều đó giúp bạn dễ dàng chèn nhật ký cụ thể hoặc xử lý lỗi tại mỗi nơi mà lỗi có thể xảy ra.
Nếu bạn không ngại một chút ma thuật macro, thì bạn có thể làm cho điều này ngắn gọn hơn trong khi thực hiện những việc khác như ghi lại lỗi với dấu vết ngăn xếp. Ví dụ:
#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;
}
Tất nhiên, điều này không thanh lịch như C ++ ngoại lệ + hủy. Ví dụ: lồng nhiều ngăn xếp xử lý lỗi trong một chức năng theo cách này không thật gọn gàng. Thay vào đó, bạn có thể muốn chia chúng ra thành các hàm phụ có khả năng xử lý lỗi tương tự, khởi tạo + hoàn thiện rõ ràng như thế này.
Điều này cũng chỉ hoạt động trong một hàm duy nhất và sẽ không tiếp tục nhảy lên ngăn xếp trừ khi người gọi cấp cao hơn thực hiện logic xử lý lỗi rõ ràng tương tự, trong khi ngoại lệ C ++ sẽ tiếp tục nhảy lên ngăn xếp cho đến khi nó tìm thấy trình xử lý thích hợp. Nó cũng không cho phép bạn ném một kiểu tùy ý mà thay vào đó chỉ là một mã lỗi.
Mã hóa một cách có hệ thống theo cách này (tức là - với một mục nhập duy nhất và một điểm thoát duy nhất) cũng giúp bạn dễ dàng chèn logic trước và sau ("cuối cùng") sẽ thực thi bất kể điều gì. Bạn chỉ cần đặt logic "cuối cùng" của mình sau nhãn END.