void* foo() {
int handle = get_some_handle();
if (handle < 0) {
goto fail;
}
void* something = some_function(handle);
if (something == NULL) {
goto free_handle;
}
void* something_else = some_other_function(something);
if (something_else == NULL) {
goto free_something;
}
return something_else;
free_something:
free_something(something);
free_handle:
free_handle(handle);
fail:
return NULL;
}
I've seen this pattern frequently in the Linux source code. I think this is an example of a case where usage of goto improves readability and reduces errors.The goto has gotten a bad rap over the years because of Dijkstra's paper. And that paper has unduly influenced a lot of incorrect thinking. There are valid use cases for goto, and this is certainly one of them.
I use it all the time like the example above. Particularly because it makes my life so much easier when developing and debugging embedded C code across various tool chains, some of which have less functionality than others.
[edit - correct typo on Ed's name]
Again, not criticizing, genuinely want to know.
So specifically in the example above, if you called failure-handling functions instead of using goto's then when the function returned you would continue execution on the next line after the function call. In the example above, that's clearly not what you want.
Now you could add some else's after the function calls to prevent execution from continuing. i.e. to get to the appropriate step in the free_* sequence at the bottom, but that starts to look messy. So I have to admit (not being a goto-lover), the above example reads very nicely.
It conforms to the "gotos might be okay if they only jump forward" rule of thumb I've heard.
We do things that are harmful all the time, in limited appropriate situations. Cutting into your abdomen is harmful, but a skillfully used surgeon's scalpel can fix a bigger problem. That's not license to go roll around on a pile of jagged, rusty steel scrap. Missing sleep is harmful, but if you do it once in a while to keep your job or to escape a nighttime flash flood then it's helpful.
Dijkstra was intending to set the norm from which people should mindfully and occasionally deviate. The point wasn't to ban the use of labelled jumps entirely.
With C++ you can ensure that you have your destructors do the tidy up, e.g. a messy example
struct cleaner { cleaner(string *toCleanup) : m_x(toCleanup) { } ~cleaner() { delete m_x; m_x = nullptr; } };
FILE* f = fopen(...);
SCOPE_GUARD({ fclose(f); });
This is mainly used for one-off calls to some native API, where writing a proper RAII wrapper for the managed resource is not worth it."error(handle, "could not open handle", free_something)"
Most code I saw while teaching it were not good cases, but sometimes it's a very interesting technique that can make the code easier to understand and shorter. I think that use is beautiful.
But it doesn't mean there aren't bad use cases.
Regardless, yes, a ton of people do bash on it because of Dijkstra - but at the time, he had a good point. In the industry of the time (as I understand it - I was a kid when he wrote that), there was a lot of "cowboy coding" out there, with goto's "gone wild" - jumping into the middle of everywhere and everything - and producing "spaghetti code".
But as you note, it can be very useful and make things easier to read (for instance, jumping out of deeply nested if-then constructs - though I could also argue a refactor might be the better solution).
I was once part of a discussion in a forum about state machines, and one guy posted a very beautifully done state machine that used no select-case construct, but rather goto statements, but done in a tight way that mimic'ed a select-case construct. I was very impressed at the time; it was some code for PIC Basic IIRC.
In time, I've changed my views from seeing goto as "always bad", to "can be very useful, in some situations - provided you know the risks of the tool".
In other words - think very carefully before you rush into using it; maybe there's a better or cleaner way.
int main() {
goto g;
std::string s;
g: return 0;
}
while this will compile, but destructor is guaranteed to be called: int main() {
{
std::string s;
goto g;
return 1;
}
g: return 0;
}
Now, longjmp is another matter. That thing is basically verboten in any sane C++ environment (and consequently, C libraries that use it across API boundary are very painful to use from C++; R hosting API is a great example of that).But instead of a measured response to make it the last tool you reach for, it's been made into a pariah.
The use of goto and similar jumps in programming languages has been subject to intensive debate, starting from an article by Dijkstra [1968]. Still today you will find people that seriously object code as it is given here, but let us try to be pragmatic about that: code with or without goto can be ugly and hard to follow.