Reputation: 61
Everything I have ever been told is that go to's are evil and stay away from them, but I think they may help me here (?). I would like to provide the user an option to restart the application when an exception is caught and am having a bit of trouble wrapping my head around what to do...
My application will be monitored by another process, but there are some exceptions where I want to the user to be able to decide what to do without returning control to the calling process.
Is something like this "acceptable"? Any other suggestions?
Thanks so much!
int main(){
initialize:
try{
//do things
}
catch(...)
{
cout<<"Would you like to try initializing again?"<<endl;
//if yes
goto initialize;
//if not
abort(); //or something...
}
return 0;
}
Upvotes: 6
Views: 5401
Reputation: 4992
I really like this use of goto
. As long as it's kept in a very small and local scope, it really avoids the spaghetti that everyone fears and actually makes things clearer than the alternatives.
try_again:
try {
do_something();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
goto try_again;
}
If I write the logic in other ways, I add more variables, more logic, more lines, more indenting. I don't like this while loop because our variable name doesn't make sense for the first pass, and we pollute our business logic by adding conditions to exit the loop.
bool try_again = true;
while(try_again) {
try {
do_something();
try_again = false;
} catch( const std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
A do
/while
loop makes it a little better: we need to do something to our special variable in the error handling to try again. But we still have extra variables, logic, and nesting. Also, do
/whiles
are super uncommon and take a little extra brain power to recall while reading.
bool try_again;
do {
try_again = false;
try {
do_something();
} catch( const std::exception& e) {
std::cerr << e.what() << std::endl;
try_again = true;
}
} while (try_again);
The original while
could be simplified with break
, and this is probably the nicest variant of the loops, but it hurts if we need another little for-loop in there, or a switch statement.
while(true){
try {
do_something();
break;
} catch( const std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
I would argue that the goto
solution is clearer than both of those loops. Before posting, I barely caught a last-moment bug in my do
/while
loop. Another nice thing about the goto
solution is if you do screw up with the label location, an error should occur:
try {
try_again:
do_something();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
goto try_again;
}
error: cannot jump from this goto statement to its label
note: jump bypasses initialization of try block
Upvotes: 0
Reputation: 11
Yes. Based on the C++ draft Standard:
goto statement can be used to transfer control out of a try block or handler.
For example, this code is legal:
lab: try {
T1 t1;
try {
T2 t2;
if (condition)
goto lab;
} catch(...) { /* handler 2 */ }
} catch(...) { /* handler 1 */ }
However, it is important to mention that:
When this happens, each variable declared in the try block will be destroyed in the context that directly contains its declaration.
Upvotes: 1
Reputation: 170499
Yes, technically it is okay, but usual "goto considered harmful" considerations apply.
Upvotes: 5
Reputation: 70263
A goto
can always be avoided, giving cleaner code.
By the way, the same goes for break
s outside a switch
. The continue
keyword is marginally less condemnable, because at least it honors the enclosing loop's condition.
It is important to catch exceptions at the right place - where you can handle the condition most effectively.
And if a condition turns out to be inconvenient (like the "try again?" in my case), consider negating it ("fail?") for cleaner structure.
// Tries until successful, or user interaction demands failure.
bool initialize() {
for ( ;; ) {
try {
// init code
return true;
}
catch ( ... ) {
cout << "Init Failed. Fail Program?" << endl;
if ( yes ) {
return false;
}
}
}
}
int main() {
if ( ! initialize() ) {
return EXIT_FAILURE;
}
// rest of program
return EXIT_SUCCESS;
}
Notes: This does not use goto
or break
, and it does not recurse (especially not from within a catch
block).
Upvotes: 5
Reputation: 64223
Normal way of handling exceptions is at the place where you can do something. It is obvious that the place you are trying to handle them is not right, since you have to use goto.
So, something like this :
void mainLoop() // get user settings, process, etc
{
try
{
// 1) get user settings
// 2) process data
// 3) inform of the result
}
catch( const exception_type & e)
{
// inform of the error
}
}
int main()
{
try
{
while(true)
mainLoop();
}
catch(...)
{
std::cout<<"an unknown exception caught... aborting() " << std::endl;
}
}
Upvotes: 1
Reputation: 2533
The original quote was (I believe) "uncontrolled use of goto considered harmful". Gotos can be useful, but must be used in a controlled fashion. There is one programming technique, to create reentrant subroutines, dependent on the state of the program or data, that positively demands directed jumps. Though this technique may well be considered old fashioned, I understand that it is still used, but is hidden by more modern language and compiler features. The point of the control is that you must stop and ask yourself, not just "is there a more structured way of doing the same thing" - @Justin - but also "under what specific conditions will I use a goto?" Being convenient is probably not a sufficient condition to use it, without this wider answer.
Upvotes: 2
Reputation:
you could try :
int main()
{
while(true)
{
try
{
program();
}
catch(std::exception& e)
{
std::cout << "Start again?" << std::endl;
//Check ...
if(!go_on)
break;
}
}
return 0;
}
Upvotes: 6
Reputation: 17762
Why not like this?
while(true){
//Do stuff
if(exit){
break;
}
}
or
continue = true;
do{
//Do stuff
if(exit){
continue = false;
}
}while(continue);
Upvotes: 9