Ok, in that case, I rest my case. However, that aside (I'm really not about starting flame wars) I think that all flow control structures--if, for, while, do-while, switch-case, break, continue, and even functions--are essentially glorified gotos. After all, they all do the same thing -- they create a non-linear execution path through the code by instantaneously jumping from one place either forward or backward. The real difference between them is that goto can be used to go
anywhere, whereas the other flow control structures have a clear entry point and exit point. This is why it is easy to reason about while() loops and for() loops and not-so-easy to reason about misused gotos. And this is why nobody argues about the use of control structures. But not all gotos are bad, I agree. For example, what is the difference between
1 2 3
|
for( i = 0; i < 10; ++i )
if( i == 5 )
break;
|
and
1 2 3 4 5 6
|
for( i = 0; i < 10; ++i )
if( i == 5 )
goto done;
// No code being skipped over...
done:
|
[Ok, yes, stupid examples. Just to illustrate my point.]
As long as it is clear that the "done" label is not used anywhere else (which takes a slight amount of effort), from a complexity standpoint, they are identical. Heck, the generated code might even be identical.
Purists of course will say to use break instead of goto, but in reality, in the above example there is no difference.
When you start using gotos to skip over arbitrary amounts of code
1 2 3 4 5 6 7 8 9 10 11 12
|
int j = 0;
for( i = 0; i < 10; ++i )
if( i == rand() % 10 )
goto done;
// Lots of code here:
// ...
j = 5;
done:
// More code here
// Is j == 0 or 5 here?
|
that's when it gets very hard to reason about the code. For example, what is the value of j post the "done" label above? Well, in this example you can't know, but even when you can know, it might require a few braincells to determine it. Fewer braincells required to understand code is always better. In my opinion, this example is a bad example of goto usage.
But C++ has issues with goto that make things even worse:
1 2 3 4 5 6 7 8 9 10 11
|
for( i = 0; i < 10; ++i )
if( i == rand() % 10 )
goto done;
// Lots of code here:
// ...
string hello_world = "Hello World!";
done:
// More code here
cout << hello_world;
|
The problem here is that hello_world's constructor is never run if the goto is executed, which means that the cout crashes. But if the goto is not executed, the code works fine. gotos in C++ allow you to bypass initialization of variables, and that is an even more compelling reason than understandability to avoid gotos in C++.
[For those who claim that a legitimate reason to bypass initialization is because construction of the object is either very expensive or causes unwanted side effects, I say that is what boost::optional is for.]