Using goto to cleanly exit the loop

I have a question about using goto statements in C ++. I understand that this topic is controversial and I am not interested in any broad tips or arguments (I usually deviate from using goto ). Rather, I have a specific situation and I want to understand if my solution, which uses goto instructions, is good or not. I would not call myself new to C ++, but I would not classify myself as a programmer on a professional level. The part of the code that raised my question rotates in an infinite loop after starting. The overall stream stream in pseudo-code is as follows:

 void ControlLoop::main_loop() { InitializeAndCheckHardware(pHardware) //pHardware is a pointer given from outside //The main loop while (m_bIsRunning) { simulated_time += time_increment; //this will probably be += 0.001 seconds ReadSensorData(); if (data_is_bad) { m_bIsRunning = false; goto loop_end; } ApplyFilterToData(); ComputeControllerOutput(); SendOutputToHardware(); ProcessPendingEvents(); while ( GetWallClockTime() < simulated_time ) {} if ( end_condition_is_satisified ) m_bIsRunning = false; } loop_end: DeInitializeHardware(pHardware); } 

The pHardware pointer is passed from outside the ControlLoop object and has a polymorphic type, so it makes no sense for me to use RAII and create and destroy the hardware interface itself inside main_loop. I believe that I could have pHardware create a temporary object that is a "session" or "use" of hardware that could be automatically cleared when I exit main_loop, but I'm not sure if this idea will become more clear to anyone otherwise I intend. There will be only three exits from the cycle: firstly, if bad data is read from external equipment; the second is if ProcessPendingEvents () indicates an interrupt initiated by the user, which simply causes m_bIsRunning to become false; and the last - if the final condition is satisfied at the bottom of the cycle. Perhaps I should also note that main_loop could be started and completed several times during the duration of the ControlLoop object, so after that it should exit using m_bIsRunning = false .

Also, I understand that I could use the break keyword here, but most of these pseudo-code calls inside main_loop are not actually encapsulated as functions, simply because they will need to have many arguments, or they all need access to member variables. In my opinion, both of these cases would be more confusing than just leaving main_loop as a longer function, and because of the length of the large while loop, an expression like goto loop_end seems to me more understandable.

Now to the question: will this solution be inconvenient for you if you have to write it in your own code? This seems a bit wrong to me, but then I never used the goto statement before in C ++ code, so my request for help is from experts. Are there any other basic ideas that I'm missing to make this code clearer?

Thanks.

+6
source share
7 answers

With your only singular state that causes the loop to break earlier, I would just use break . There is no need for goto , for which break .

However, if any of these function calls may throw an exception, or if you need a few break , I would prefer a RAII-style container, this is what destructors need. You always make a call to DeInitializeHardware , so ...

 // todo: add error checking if needed class HardwareWrapper { public: HardwareWrapper(Hardware *pH) : _pHardware(pH) { InitializeAndCheckHardware(_pHardware); } ~HardwareWrapper() { DeInitializeHardware(_pHardware); } const Hardware *getHardware() const { return _pHardware; } const Hardware *operator->() const { return _pHardware; } const Hardware& operator*() const { return *_pHardware; } private: Hardware *_pHardware; // if you don't want to allow copies... HardwareWrapper(const HardwareWrapper &other); HardwareWrapper& operator=(const HardwareWrapper &other); } // ... void ControlLoop::main_loop() { HardwareWrapper hw(pHardware); // code } 

Now, no matter what happens, you always call DeInitializeHardware when this function returns.

+3
source

Avoiding using goto is a pretty solid thing for object-oriented development in general.

In your case, why not just use break to exit the loop?

 while (true) { if (condition_is_met) { // cleanup break; } } 

As for your question: your use of goto will make me uncomfortable. The only reason break less readable is your access to not being a strong C ++ developer. For any experienced developer of a C-like language, break would be better to read and also provide a cleaner solution than goto .

In particular, I simply do not agree that

 if (something) { goto loop_end; } 

more readable than

 if (something) { break; } 

which literally says the same thing with the built-in syntax.

+5
source

UPDATE

If your main problem is that the while loop is too long, then you should strive to make it shorter, C ++ is the OO language, and OO is for divided things into small pieces and components, even in a common language other than OO, we usually still think that we should break the method / loop into a small one and make it easy to read. If there are 300 lines in a loop, regardless of whether the / goto break really saves you time, is that not so?

UPDATE

I don’t mind goto , but I won’t use it here, like you, I prefer to use break , usually to the developer, that he saw break , he knows that it means goto until the end of while, and with this m_bIsRunning = false he can it's easy to see that it actually exits the loop in seconds. Yes goto can save time in seconds to figure it out, but it can also make people nervous about your code.

What I can imagine that I am using goto is to exit the loop with two levels:

 while(running) { ... while(runnning2) { if(bad_data) { goto loop_end; } } ... } loop_end: 
+3
source

Instead of using goto you should use break; to remove cycles.

+1
source

Depending on the situation, there are several goto alternatives: break , continue and return .

However, you need to keep in mind that both break and continue limited in that they only affect the innermost loop. return on the other hand, does not affect this limitation.

In general, if you use goto to exit a specific area, then you can reorganize another function and return instead. This will probably make the code more readable as a bonus:

 // Original void foo() { DoSetup(); while (...) { for (;;) { if () { goto X; } } } label X: DoTearDown(); } // Refactored void foo_in() { while (...) { for (;;) { if () { return; } } } } void foo() { DoSetup(); foo_in(); DoTearDown(); } 

Note. If your body function cannot fit comfortably on your screen, you are doing it wrong.

+1
source

Goto is not good practice to exit the loop when break is an option.

In addition, in complex procedures, it is good to have only one exit logic (with clearing) located at the end. Goto is sometimes used to jump to return logic.

An example from the QEMU vmdk block driver:

 static int vmdk_open(BlockDriverState *bs, int flags) { int ret; BDRVVmdkState *s = bs->opaque; if (vmdk_open_sparse(bs, bs->file, flags) == 0) { s->desc_offset = 0x200; } else { ret = vmdk_open_desc_file(bs, flags, 0); if (ret) { goto fail; } } /* try to open parent images, if exist */ ret = vmdk_parent_open(bs); if (ret) { goto fail; } s->parent_cid = vmdk_read_cid(bs, 1); qemu_co_mutex_init(&s->lock); /* Disable migration when VMDK images are used */ error_set(&s->migration_blocker, QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED, "vmdk", bs->device_name, "live migration"); migrate_add_blocker(s->migration_blocker); return 0; fail: vmdk_free_extents(bs); return ret; } 
0
source

I see a lot of people offering break instead of goto . But break not "better" (or "worse") than goto .

The Inquisition vs. goto effectively began work with the Dijkstra document “Go to considered harmful” back in 1968, when the spaghetti code was the rule and things like block-structured if and while if were still considered advanced. ALGOL 60 had them, but it was, in fact, the research language used by scientists (see Today ML); Fortran, one of the dominant languages ​​at the time, did not receive them for another 9 years!

The main points in the work of Dijkstra are:

  • People are well versed in spatial reasoning, and program-structured programs benefit from this because program actions that occur side by side in time are described next to each other in “space” (program code);
  • If you avoid goto in all its various forms, then it is possible to find out about the possible states of the variables in each lexical position in the program. In particular, at the end of the while you know that this loop condition must be false. This is useful for debugging. (Dijkstra doesn't really say that, but you can do it.)

break , like goto (and early return s, and exceptions ...), reduces (1) and eliminates (2). Of course, using break often avoids writing complex logic for the while condition, getting you a clear profit in comprehensibility - and it’s the same for goto .

0
source

Source: https://habr.com/ru/post/927221/


All Articles