I know that using the GOTO statement has been made to be a coding 'taboo,' but why? And what is so bad about using it multiple times? I mean, it seems helpful.
Well, for one there is almost always another way to go about using it. Two, it does not care about structure at all, so you could easily break other loops.
GOTO was quite popular before structured algol-influenced languages came to scene - and C among them.
Here it appeared that program flow could almost always be effectively designed without "gotos", while inclusion of them destroys structured idea of the program - it is possible to make jump inside the middle of the inner block (like loop or condition) etc.
This is not big problem for smaller programs written by school students, but in professional development this leads to complicated mess with unnecessary increasing cost of further support.
However, there are few more points to consider:
- jumping out of nested loops is an important feature lost with decline of gotos, and because of that many languages make some provisions - so Java allows labeling of loops to use "break" and "continue" operators as reduced gotos - and PHP allows to add value to break to show how many loops should be exited;
- goto is nevertheless important in languages which do not greatly employ structured statements - especially in assemblers;
- goto is important part of many esoteric languages, which are the creations of programmer's wild humor;
- some maniacs insist that not only "gotos" should be banned, but also "break" and "continue" operators.
#include <iostream>
#include <chrono>
int main()
{
std::cout << "Enter a value to compute: ";
int value;
std::cin >> value;
int iterations;
std::cout << "Enter the iteration count: ";
std::cin >> iterations;
volatileint arr[7];
float lambdatime;
{
auto begin = std::chrono::high_resolution_clock::now();
for (volatileint i = 0; i < iterations; ++i)
{
[&]() -> void
{
for (volatileint a(0); a < 100; ++a)
for (volatileint b(0); b < 100; ++b)
for (volatileint c(0); c < 100; ++c)
for (volatileint d(0); d < 100; ++d)
for (volatileint e(0); e < 100; ++e)
for (volatileint f(0); f < 100; ++f)
for (volatileint g(0); g < 100; ++g)
if (a + b + c + d + e + f + g == value)
{
arr[0] = a;
arr[1] = b;
arr[2] = c;
arr[3] = d;
arr[4] = e;
arr[5] = f;
arr[6] = g;
return;
}
}();
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Lambda implementation took: " << (lambdatime = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count()) << " ns" << std::endl;
}
float exceptiontime;
{
auto begin = std::chrono::high_resolution_clock::now();
for (volatileint i = 0; i < iterations; ++i)
{
try
{
for (volatileint a(0); a < 100; ++a)
for (volatileint b(0); b < 100; ++b)
for (volatileint c(0); c < 100; ++c)
for (volatileint d(0); d < 100; ++d)
for (volatileint e(0); e < 100; ++e)
for (volatileint f(0); f < 100; ++f)
for (volatileint g(0); g < 100; ++g)
if (a + b + c + d + e + f + g == value)
{
arr[0] = a;
arr[1] = b;
arr[2] = c;
arr[3] = d;
arr[4] = e;
arr[5] = f;
arr[6] = g;
throw'0';
}
}
catch (...)
{}
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Exception implementation took: " << (exceptiontime = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count()) << " ns" << std::endl;
}
float gototime;
{
auto begin = std::chrono::high_resolution_clock::now();
for (volatileint i = 0; i < iterations; ++i)
{
for (volatileint a(0); a < 100; ++a)
for (volatileint b(0); b < 100; ++b)
for (volatileint c(0); c < 100; ++c)
for (volatileint d(0); d < 100; ++d)
for (volatileint e(0); e < 100; ++e)
for (volatileint f(0); f < 100; ++f)
for (volatileint g(0); g < 100; ++g)
if (a + b + c + d + e + f + g == value)
{
arr[0] = a;
arr[1] = b;
arr[2] = c;
arr[3] = d;
arr[4] = e;
arr[5] = f;
arr[6] = g;
goto endlabel;
}
endlabel:;
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Goto implementation took: " << (gototime = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count()) << " ns" << std::endl;
}
}
Enter a value to compute: 283
Enter the iteration count: 2000
Lambda implementation took: 4.00983e+009 ns
Exception implementation took: 4.09506e+009 ns
Goto implementation took: 4.15469e+009 ns
Enter a value to compute: 283
Enter the iteration count: 20000
Lambda implementation took:40053040900 ns
Exception implementation took: 40620207300 ns
Goto implementation took: 40361149500 ns
Enter a value to compute: 99
Enter the iteration count: 100000
Lambda implementation took: 25016700 ns
Exception implementation took: 197130600 ns
Goto implementation took: 23015400 ns
It appears that; when the loop does not iterate through the outermost loops, but only through the innermost one, as in the last example with 99, goto is faster.
Exceptions are always slower.
Lambdas are quite good in most cases.
Would anyone benchmark this on their machines? We could compare results to see which is generally better :3.
Enter a value to compute: 283
Enter the iteration count: 2000
Lambda implementation took: 4.10259e+09 ns
Exception implementation took: 4.06926e+09 ns
Goto implementation took: 4.06944e+09 ns
Enter a value to compute: 283
Enter the iteration count: 20000
Lambda implementation took: 4.05306e+10 ns
Exception implementation took: 3.9017e+10 ns
Goto implementation took: 3.9865e+10 ns
Enter a value to compute: 99
Enter the iteration count: 10000
Lambda implementation took: 3.50846e+06 ns
Exception implementation took: 2.31812e+07 ns
Goto implementation took: 2.61926e+06 ns
GCC 4.8 on the same 64bit Linux box
Enter a value to compute: 283
Enter the iteration count: 2000
Lambda implementation took: 4.12653e+09 ns
Exception implementation took: 4.1709e+09 ns
Goto implementation took: 4.16547e+09 ns
Enter a value to compute: 283
Enter the iteration count: 20000
Lambda implementation took: 4.06953e+10 ns
Exception implementation took: 4.14912e+10 ns
Goto implementation took: 4.11446e+10 ns
Enter a value to compute: 99
Enter the iteration count: 100000
Lambda implementation took: 3.13875e+07 ns
Exception implementation took: 1.85117e+08 ns
Goto implementation took: 2.49494e+07 ns
1) No comment. I'm not familiar enough with lambdas to have any insight into good/bad practice regarding them.
2) I really don't like using exceptions to perform normal-behaviour actions. I'd prefer to use them only for, well, exceptional circumstances.
3) This is one of the least heinous uses of goto. If it was really, really necessary to have such a deeply nested loop, and it was really, really necessary to break out of all the loops at once, it might be the lesser of several evils. But I'd want to see it really clearly commented :)
To be frank - and I appreciate this may sound glib - if you wind up needing to write a loop that deeply nested, and that messy to break out of using more conventional methods, then there's probably something wrong with the design of your code in the first place. It would be better to address that, rather than choosing which unusual coding trick to use.
Perhaps you are right; tho I guess there are cases where we have at least a 2-depth loop, using a boolean evaluator on each of these that signals whether to break out should be more expensive and complex to handle.
Should a C++ language construct be created?
break 2; // Break from the third loop, considering the inner loop: loop 0.
This would imply that this should also be implemented.
Thanks for provided examples of exiting inner loops.
My humble opinion is that exiting by throwing exception greatly stinks of bad practices and bad style, though variant with "return" from closure is less or more OK. I personally prefer "goto" in such case if I have nothing better.
Should a C++ language construct be created?
I suppose that if we can have dedicated statement for such things - it is anyway better than use some clumsy crutches.
However, I prefer "break" and "continue" with named labels rather than with numbers, like in Java:
1 2 3 4 5 6 7 8
outerLoop:
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (a[i] == a[j]) {
break outerLoop;
}
}
}
Label could be placed only before loop (since it is not label but marker of the loop). The difference from numbers is that:
- adding or removing one loop (or switch like in PHP) does not change the behavior;
- there is no temptation to pass variable instead of number.
Aries might I ask why you used volatile for your loops? It seems unnecessary. AFAIK volatile is so the compiler will not optimize it and it mainly used when you have the hardware modifying the variables externally.
Enter a value to compute: 238
Enter the iteration count: 2000
Lambda implementation took: 2.4024e+009 ns
Exception implementation took: 2.1528e+009 ns
Goto implementation took: 2.106e+009 ns
Enter a value to compute: 238
Enter the iteration count: 20000
Lambda implementation took: 2.39928e+010 ns
Exception implementation took: 2.12472e+010 ns
Goto implementation took: 2.10756e+010 ns
Enter a value to compute: 99
Enter the iteration count: 100000
Lambda implementation took: 3.12e+007 ns
Exception implementation took: 6.55201e+008 ns
Goto implementation took: 3.12e+007 ns
Enter a value to compute: 99
Enter the iteration count: 1000000
Lambda implementation took: 3.74401e+008 ns
Exception implementation took: 6.39601e+009 ns
Goto implementation took: 2.652e+008 ns
The last because 100k iterations doesn't take long enough to get an accurate read with the default timer granularity on Windows.