A Conversation for The GOTO Statement
A proper use of GOTO
Chance Started conversation Jul 18, 2000
In most languages, especially Object Oriented Languages, goto's are frowned upon. However, over the years, I've noticed one extremely good use of goto. Goto's are very useful in handing errors and exceptional conditions in environments where built-in exception handling is unavailable.
So, when would exception handling be unavailable? Embedded systems and catankerous clients are usually the cause. Exception handling requrires some hardware support for proper implementation and some embedded systems do not support it. As for clients - they may have a legacy C/C++ system that cannot have exception handling turned on.
Ok, so you cannot use exception handling. How does GOTO help? Like this: Suppose you had to make three or four system calls to get some information (not uncommon with Microsoft APIs). Any one of them could fail. You may be tempted to write a series of nested IF statements, with the last line of the IF clause releasing the resource and an ELSE clause for each to handle errors. This quickly produces a maintanance nightmare.
Instead, use GOTOs. You can write the entire method with no nesting. Here's an example:
bool MyClass::MyMethod()
{
bool iRet = false;
OBJ1* pobj1 = NULL;
OBJ2* pobj2 = NULL;
OBJ3* pobj3 = NULL;
if (! Api1(&pobj1) ) goto exit;
if (! Api2(pobj1, &pobj2) ) goto exit;
if (! Api3(pobj2, &pobj3) ) goto exit;
// do something with pobj3
pobj3->SuperFantasticUsefulMethod();
iRet = true;
exit:
if (pobj1) pobl1->Release();
if (pobj2) pobj2->Release();
if (pobj3) pobj3->Release();
return iRet;
}
This code is embarrasingly easy to maintain and understand. Errors anywhere will automatically cleanup and return the failure. If everything works, it will also clean things up. You can easily verify all resources are freed.
There is one danger here. You MUST NOT declare any object inside of braces once you pass the first GOTO statement. If you call goto from inside a scope, it will not call destructors. If you didn't follow this argument, sorry. Just follow this rule:
If you use GOTO for error handling this way, you may not have any scoping braces. This means no FOR loops and such. This isn't as hard as it seems, just declare a new method for the guts of a FOR loop and you're there.
A proper use of GOTO
Dudemeister Posted Jul 19, 2000
But it's much more fun when you use the label "Hell" rather than "exit".
Self documenting code - even COBOL programmers would appreciate.
A proper use of GOTO
JonBob Posted Jul 19, 2000
Makes me think of one of my favorite Perl features: being able to tell the computer to do something "or die".
A proper use of GOTO
Imaldris Posted Aug 11, 2000
"Cake or death?" "Death please, no, no, no I meant cake...!"
A proper use of GOTO
Old Hairy Posted Sep 8, 2003
I am one that would oppose any attempt to abolish the GOTO, and have been a practitioner (and lover) of C for more than two decades. I am less enamoured of C++, but OOPS, yours is a good example only of a redundant GOTO.
Let me first quote the example routine, but embellished with my comments relating to its operation. Then modify it to eliminate the goto's, without changing its operation. Then collapse several if's into one, also without changing its operation. And then finally say what the scope rules for goto's really are, and the attendant dangers of using them.
The original example is functionally as follows, with comments only added.
bool MyClass::MyMethod()
{
bool iRet = false;
OBJ1* pobj1 = NULL;
OBJ2* pobj2 = NULL;
OBJ3* pobj3 = NULL;
// only this call of Api1 can make pobj1 non NULL
// thus Api1 must allocate space for an OBJ1 ever to exist
if (! Api1(&pobj1) ) goto exit;
// only this call of Api2 can make pobj2 non NULL
// thus Api2 must allocate space for an OBJ2 ever to exist
// pobj1 cannot change because of call by value
// the OBJ1 that pobj1 points to may change, const is absent
// thus OBJ1 objects are constructed in Api1 or Api2
if (! Api2(pobj1, &pobj2) ) goto exit;
// only this call of Api3 can make pobj3 non NULL
// pobj2 cannot change because of call by value
// the OBJ2 that pobj2 points to may change, const is absent
// thus OBJ2 objects are constructed in Api2 or Api3
if (! Api3(pobj2, &pobj3) ) goto exit;
// if Api3 returns non-zero, that must imply OBJ3 constructed
// otherwise the following call of a function via a pointer is doomed
pobj3->SuperFantasticUsefulMethod();
iRet = true;
exit:
if (pobj1) pobl1->Release(); // destroy OBJ1, else memory leak ??
if (pobj2) pobj2->Release(); // destroy OBJ2, else memory leak ??
if (pobj3) pobj3->Release(); // destroy OBJ3, else memory leak ??
return iRet;
}
The following performs IDENTICALLY, but without goto's.
bool MyClass::MyMethod()
{
bool iRet = false;
OBJ1* pobj1 = NULL;
OBJ2* pobj2 = NULL;
OBJ3* pobj3 = NULL;
if ( Api1(&pobj1) ) // this if is unfinished, needs a statement
if ( Api2(pobj1, &pobj2) ) // this if is unfinished, needs a statement
if ( Api3(pobj2, &pobj3) ) // this if is unfinished, needs a statement
// start (compound) statement
{
// nothing created or destroyed in this scope
pobj3->SuperFantasticUsefulMethod();
iRet = true;
} // end of compound statement, completing all three if's
if (pobj1) pobl1->Release();
if (pobj2) pobj2->Release();
if (pobj3) pobj3->Release();
return iRet;
}
Also IDENTICAL operation, but collasping the if's into one.
bool MyClass::MyMethod()
{
bool iRet = false;
OBJ1* pobj1 = NULL;
OBJ2* pobj2 = NULL;
OBJ3* pobj3 = NULL;
if ( Api1(&pobj1) && Api2(pobj1, &pobj2) && Api3(pobj2, &pobj3) )
{
pobj3->SuperFantasticUsefulMethod();
iRet = true;
} // end of if
if (pobj1) pobl1->Release();
if (pobj2) pobj2->Release();
if (pobj3) pobj3->Release();
return iRet;
}
Now for the rules. You cannot call a goto, only a function (but a called function can start with a goto). You can execute a goto, and that causes a jump to its target label (with no chance of return). The label must be in the same function as the goto, and this is a scope rule enforced by compilers. Failure to comply will attract missing label messages on offending goto's, and might attract unused label messages on offending labels. The dangers arising from the use of goto's are that either necessary operations become omitted, or that undesirable operations become performed. These are the usual problems arising in software production anyway, no more and no less. It is true that there are special problems in C++ with constructors and destructors, which can be compiler generated calls to compiler generated functions, and that such destructors in particular are almost invisible in the source code, a closing brace at the end of a scope being all that is required to invoke them. The penalty for abuse of either constructors or destructors is always severe, ranging from application (or system) crashes, to difficult to detect memory leaks.
That anyway is my opinion. I am sorry to have been so critical, but hope to have been of some help.
Key: Complain about this post
A proper use of GOTO
More Conversations for The GOTO Statement
Write an Entry
"The Hitchhiker's Guide to the Galaxy is a wholly remarkable book. It has been compiled and recompiled many times and under many different editorships. It contains contributions from countless numbers of travellers and researchers."