It is considered acceptable practice to isolate these decisions in a function and use return
s instead of break
s. While all these checks correspond to the same level of abstraction as of the function, it is quite logical approach.
For example:
void foo(...)
{
if (!condition)
{
return;
}
...
if (!other condition)
{
return;
}
...
if (!another condition)
{
return;
}
...
if (!yet another condition)
{
return;
}
...
// Some unconditional stuff
}
There are times when using goto
is actually the RIGHT answer - at least to those who are not brought up in the religious belief that "goto
can never be the answer, no matter what the question is" - and this is one of those cases.
This code is using the hack of do { ... } while(0);
for the sole purpose of dressing up a goto
as a break
. If you are going to use goto
, then be open about it. It's no point in making the code HARDER to read.
A particular situation is just when you have a lot of code with quite complex conditions:
void func()
{
setup of lots of stuff
...
if (condition)
{
...
...
if (!other condition)
{
...
if (another condition)
{
...
if (yet another condition)
{
...
if (...)
...
}
}
}
....
}
finish up.
}
It can actually make it CLEARER that the code is correct by not having such a complex logic.
void func()
{
setup of lots of stuff
...
if (!condition)
{
goto finish;
}
...
...
if (other condition)
{
goto finish;
}
...
if (!another condition)
{
goto finish;
}
...
if (!yet another condition)
{
goto finish;
}
...
....
if (...)
... // No need to use goto here.
finish:
finish up.
}
Edit: To clarify, I'm by no means proposing the use of goto
as a general solution. But there are cases where goto
is a better solution than other solutions.
Imagine for example that we are collecting some data, and the different conditions being tested for are some sort of "this is the end of the data being collected" - which depends on some sort of "continue/end" markers that vary depending on where you are in the data stream.
Now, when we're done, we need to save the data to a file.
And yes, there are often other solutions that can provide a reasonable solution, but not always.
You can use a simple continuation pattern with a bool
variable:
bool goOn;
if ((goOn = check0())) {
...
}
if (goOn && (goOn = check1())) {
...
}
if (goOn && (goOn = check2())) {
...
}
if (goOn && (goOn = check3())) {
...
}
This chain of execution will stop as soon as checkN
returns a false
. No further check...()
calls would be performed due to short-circuiting of the &&
operator. Moreover, optimizing compilers are smart enough to recognize that setting goOn
to false
is a one-way street, and insert the missing goto end
for you. As the result, the performance of the code above would be identical to that of a do
/while(0)
, only without a painful blow to its readability.
Try to extract the code into a separate function (or perhaps more than one). Then return from the function if the check fails.
If it's too tightly coupled with the surrounding code to do that, and you can't find a way to reduce the coupling, look at the code after this block. Presumably, it cleans up some resources used by the function. Try to manage these resources using an RAII object; then replace each dodgy break
with return
(or throw
, if that's more appropriate) and let the object's destructor clean up for you.
If the program flow is (necessarily) so squiggly that you really need a goto
, then use that rather than giving it a weird disguise.
If you have coding rules that blindly forbid goto
, and you really can't simplify the program flow, then you'll probably have to disguise it with your do
hack.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With