C++ Programming
The goto
keyword is discouraged as it makes it difficult to follow the program logic, this way inducing to errors. The goto
statement causes the current thread of execution to jump to the specified label.
- Syntax
label:
statement(s);
goto label;
In some rare cases, the goto
statement allows to write uncluttered code, for example, when handling multiple exit points leading to the cleanup code at a function exit (and neither exception handling or object destructors are better options). Except in those rare cases, the use of unconditional jumps is a frequent symptom of a complicated design, as the presence of many levels of nested statements.
In exceptional cases, like heavy optimization, a programmer may need more control over code behavior; a goto
allows the programmer to specify that execution flow jumps directly and unconditionally to a desired label. A label is the name given to a label statement elsewhere in the function.
A goto
can, for example, be used to break out of two nested loops. This example breaks after replacing the first encountered non-zero element with zero.
for (int i = 0; i < 30; ++i) {
for (int j = 0; j < 30; ++j) {
if (a[i][j] != 0) {
a[i][j] = 0;
goto done;
}
}
}
done:
/* rest of program */
Although simple, they quickly lead to illegible and unmaintainable code.
// snarled mess of gotos
int i = 0;
goto test_it;
body:
a[i++] = 0;
test_it:
if (a[i])
goto body;
/* rest of program */
is much less understandable than the equivalent:
for (int i = 0; a[i]; ++i) {
a[i] = 0;
}
/* rest of program */
Gotos are typically used in functions where performance is critical or in the output of machine-generated code (like a parser generated by yacc.)
The goto
statement should almost always be avoided, but there are rare cases where it enhances the readability of code. One such case is an "error section".
Example
#include <new>
#include <iostream>
...
int *my_allocated_1 = NULL;
char *my_allocated_2 = NULL, *my_allocated_3 = NULL;
my_allocated_1 = new (std::nothrow) int[500];
if (my_allocated_1 == NULL)
{
std::cerr << "error in allocated_1" << std::endl;
goto error;
}
my_allocated_2 = new (std::nothrow) char[1000];
if (my_allocated_2 == NULL)
{
std::cerr << "error in allocated_2" << std::endl;
goto error;
}
my_allocated_3 = new (std::nothrow) char[1000];
if (my_allocated_3 == NULL)
{
std::cerr << "error in allocated_3" <<std::endl;
goto error;
}
return 0;
error:
delete [] my_allocated_1;
delete [] my_allocated_2;
delete [] my_allocated_3;
return 1;
This construct avoids hassling with the origin of the error and is cleaner than an equivalent construct with control structures. It is thus less error prone.