# 3 Or the honest-to-god goto model:
int do_work(int stuff) { int res1 = 0, res2 = 0, res3 = 0; int success = 0; res1 = acquire_one_resource(stuff); if (!res1) goto cleanup; res2 = acquire_another_resource(stuff); if (!res2) goto cleanup; res3 = acquire_more_resources(stuff); if (!res3) goto cleanup; /* etc. */ success = 1; cleanup: /* here the only requirement is that the cleanup process * ignores unacquired resources, perhaps with some minor * logic if you have pointers to resources (unusual) */ free_resource(res3); free_resource(res2); // would you look at that, all the cleanup code free_resource(res1); // in one single spot, no code duplication return success; }
I use this kind of thing fairly frequently -- one additional way (when needed) is you could have 3 different cleanup target LABELs if there were complications with running each different free_resource(). call (or their possibly very different setup processing example -- 3D graphics where you have many different types of objects you have to setup all at once... )
Also its typical that this overall function only does the initializations (and the real processing is elsewhere and extensive) so the cleanups here would all be 'FAIL conditions and the return(1) would be above the cleanup block and the bottom return a return(0) -- since if you failed allocating resources-the caller needs tio handle that.
for readability (when tehre is a whole list of similar statements I also frequently do :
if ( ! (res1 = acquire_one_resource(stuff))) goto FAIL_CLEANUP;
if ( ! (res2 = acquire_another_resource(stuff))) goto FAIL_CLEANUP;
if ( ! (res3 = acquire_more_resources(stuff))) goto FAIL_CLEANUP;
And I always capitalize my goto labels to make them stand out all the more
Or as I mentioned :
if ( ! (res1 = acquire_one_resource(stuff))) goto FAIL_CLEANUP_ONE;
if ( ! (res2 = acquire_another_resource(stuff))) goto FAIL_CLEANUP_ANOTHER;
if ( ! (res3 = acquire_more_resources(stuff))) goto FAIL_CLEANUP_MORE;
Typically cleanup code is also near the bottom of the subroutine - making it the expected place to look if the code uses this pattern consistantly.
Another pattern Ive sometimes used (a backward jumping GOTO) was for local retry ( more in ancient days when data seeks/reads/writes across a network/from disk could fail at anypoint ), and another was parsing custom text file save formats with 'none or many' flavored data text lines.
Of course those were complicated by retry limits of some kind to eventually signal upward as a significant failure.
I was actually appalled that entire applications I was handed had not just no retries but often no error return checking at all on such operations.