How to avoid GOTO in C++

两盒软妹~` 提交于 2020-06-13 08:55:37

问题


I have read that GOTO is bad, but how do I avoid it? I don't know how to program without GOTO. In BASIC I used GOTO for everything. What should I use instead in C and C++?

I used GOTO in BASIC like this:

MainLoop:
INPUT string$   
IF string$ = "game" THEN   
GOTO game
ENDIF

回答1:


Usually loops like for, while and do while and functions have more or less disposed the need of using GOTO. Learn about using those and after a few examples you won't think about goto anymore. :)




回答2:


Consider the following piece of C++ code:

void broken()
{
    int i = rand() % 10;
    if (i == 0) // 1 in 10 chance.
        goto iHaveABadFeelingAboutThis;

    std::string cake = "a lie";

    // ...
    // lots of code that prepares the cake
    // ...

iHaveABadFeelingAboutThis:
    // 1 time out of ten, the cake really is a lie.
    eat(cake);

    // maybe this is where "iHaveABadFeelingAboutThis" was supposed to be?
    std::cout << "Thank you for calling" << std::endl;
}

Ultimately, "goto" is not much different than C++'s other flow-control keywords: "break", "continue", "throw", etc; functionally it introduces some scope-related issues as demonstrated above.

Relying on goto will teach you bad habits that produce difficult to read, difficult to debug and difficult to maintain code, and it will generally tend to lead to bugs. Why? Because goto is free-form in the worst possible way, and it lets you bypass structural controls built into the language, such as scope rules, etc.

Few of the alternatives are particularly intuitive, and some of them are arguably as ambiguous as "goto", but at least you are operating within the structure of the language - referring back to the above sample, it's much harder to do what we did in the above example with anything but goto (of course, you can still shoot yourself in the foot with for/while/throw when working with pointers).

Your options for avoiding it and using the language's natural flow control constructs to keep code humanly readable and maintainable:

  • Break your code up into subroutines.

Don't be afraid of small, discrete, well-named functions, as long as you are not perpetually hauling a massive list of arguments around (if you are, then you probably want to look at encapsulating with a class).

Many novices use "goto" because they write ridiculously long functions and then find that they want to get from line 2 of a 3000 line function to line 2998. In the above code, the bug created by goto is much harder to create if you split the function into two payloads, the logic and the functional.

void haveCake() {
    std::string cake = "a lie";

    // ...
    // lots of code that prepares the cake
    // ...

    eat(cake);
}

void foo() {
    int i = rand() % 10;
    if (i != 0) // 9 times out of 10
        haveCake();
    std::cout << "Thanks for calling" << std::endl;
}   

Some folks refer to this as "hoisting" (I hoisted everything that needed to be scoped with 'cake' into the haveCake function).

  • One-shot for loops.

These are not always obvious to programmers starting out, it says it's a for/while/do loop but it's actually only intended to run once.

for ( ; ; ) { // 1-shot for loop.
    int i = rand() % 10;
    if (i == 0) // 1 time in 10
         break;
    std::string cake = "a lie";
    // << all the cakey goodness.

    // And here's the weakness of this approach.
    // If you don't "break" you may create an infinite loop.
    break;
}

std::cout << "Thanks for calling" << std::endl;
  • Exceptions.

These can be very powerful, but they can also require a lot of boiler plate. Plus you can throw exceptions to be caught further back up the call stack or not at all (and exit the program).

struct OutOfLuck {};

try {
    int i = rand() % 10;
    if (i == 0)
        throw OutOfLuck();
    std::string cake = "a lie";
    // << did you know: cake contains no fat, sugar, salt, calories or chemicals?
    if (cake.size() < MIN_CAKE)
        throw CakeError("WTF is this? I asked for cake, not muffin");
}
catch (OutOfLuck&) {} // we don't catch CakeError, that's Someone Else's Problem(TM).

std::cout << "Thanks for calling" << std::endl;

Formally, you should try and derive your exceptions from std::exception, but I'm sometimes kind of partial to throwing const char* strings, enums and occasionally struct Rock.

try {
    if (creamyGoodness.index() < 11)
        throw "Well, heck, we ran out of cream.";
} catch (const char* wkoft /*what kind of fail today*/) {
    std::cout << "CAKE FAIL: " << wkoft << std::endl;
    throw std::runtime_error(wkoft);
}

The biggest problem here is that exceptions are intended for handling errors as in the second of the two examples immediately above.




回答3:


There are several reasons to use goto, the main would be: conditional execution, loops and "exit" routine.

Conditional execution is managed by if/else generally, and it should be enough

Loops are managed by for, while and do while; and are furthermore reinforced by continue and break

The most difficult would be the "exit" routine, in C++ however it is replaced by deterministic execution of destructors. So to make you call a routine on exiting a function, you simply create an object that will perform the action you need in its destructor: immediate advantages are that you cannot forget to execute the action when adding one return and that it'll work even in the presence of exceptions.




回答4:


Edsger Dijkstra published a famous letter titled Go To Statement Considered Harmful. You should read about it, he advocated for structured programming. That wikipedia article describes what you need to know about structured programming. You can write structured programs with goto, but that is not a popular view these days, for that perspective read Donald Knuth's Structured Programming with goto Statements.




回答5:


goto is now displaced by other programming constructs like for, while, do-while etc, which are easier to read. But goto still has it's uses. I use it in a situation where different code blocks in a function (for e.g., which involve different conditional checks) have a single exit point. Apart from this one use for every other thing you should use appropriate programming constructs.




回答6:


goto is not inherently bad, it has it's uses, just like any other language feature. You can completely avoid using goto, by using exceptions, try/catch, and loops as well as appropriate if/else constructs.

However, if you realize that you get extremly out of your way, just to avoid it, it might be an indiaction that it would be better to use it.

Personally I use goto to implement functions with single entry and exit points, which makes the code much more readable. This is the only thing where I still find goto usefull and actually improves the structure and readabillity of the code.

As an example:

int foo()
{
    int fd1 = -1;
    int fd2 = -1;
    int fd3 = -1;

    fd1 = open();
    if(fd1 == -1)
        goto Quit:

    fd2 = open();
    if(fd2 == -1)
        goto Quit:

    fd3 = open();
    if(fd3 == -1)
        goto Quit:

     ... do your stuff here ...

Quit:
   if(fd1 != -1)
      closefile();

   if(fd2 != -1)
      closefile();

   if(fd3 != -1)
      closefile();
}

In C++ you find, that the need for such structures might be drastically reduced, if you properly implement classes which encapsulate access to resources. For example using smartpointer are such an example.

In the above sample, you would implement/use a file class in C++, so that, when it gets destructed, the file handle is also closed. Using classes, also has the advantage that it will work when exceptions are thrown, because then the compiler ensures that all objects are properly destructed. So in C++ you should definitely use classes with destructors, to achieve this.

When you want to code in C, you should consider that extra blocks also add additional complexity to the code, which in turn makes the code harder to understand and to control. I would prefer a well placed goto anytime over a series of artifical if/else clauses just to avoid it. And if you later have to revisit the code, you can still understand it, without following all the extra blocks.




回答7:


Maybe instead of

if(something happens) 
   goto err;

err:
   print_log()

one can use :

do {

    if (something happens)
    {
       seterrbool = true;       
       break;  // You can avoid using using go to I believe
    } 

} while (false) //loop will work only one anyways
if (seterrbool)
   printlog();

It may not seem friendly because in the example above there is only one goto but will be more readable if there are many "goto" .




回答8:


This implementation of the function above avoids using goto's. Note, this does NOT contain a loop. The compiler will optimize this. I prefer this implementation.

Using 'break' and 'continue', goto statements can (almost?) always be avoided.

int foo()
{
    int fd1 = -1;
    int fd2 = -1;
    int fd3 = -1;

    do
    {
        fd1 = open();
        if(fd1 == -1)
            break;

        fd2 = open();
        if(fd2 == -1)
            break:

        fd3 = open();
        if(fd3 == -1)
            break;

         ... do your stuff here ...
    }
    while (false);

   if(fd1 != -1)
      closefile();

   if(fd2 != -1)
      closefile();

   if(fd3 != -1)
      closefile();
}



回答9:


BASIC originally is an interpreted language. It doesn't have structures so it relies on GOTOs to jump the specific line, like how you jump in assembly. In this way the program flow is hard to follow, making debugging more complicated.

Pascal, C and all modern high-level programming languages including Visual Basic (which was based on BASIC) are strongly structured with "commands" grouped into blocks. For example VB has Do... Loop, While... End While, For...Next. Even some old derivatives of BASIC support structures like Microsoft QuickBASIC:

DECLARE SUB PrintSomeStars (StarCount!)
REM QuickBASIC example
INPUT "What is your name: ", UserName$
PRINT "Hello "; UserName$
DO
   INPUT "How many stars do you want: ", NumStars
   CALL PrintSomeStars(NumStars)
   DO
      INPUT "Do you want more stars? ", Answer$
   LOOP UNTIL Answer$ <> ""
   Answer$ = LEFT$(Answer$, 1)
LOOP WHILE UCASE$(Answer$) = "Y"
PRINT "Goodbye "; UserName$
END

SUB PrintSomeStars (StarCount)
   REM This procedure uses a local variable called Stars$
   Stars$ = STRING$(StarCount, "*")
   PRINT Stars$
END SUB

Another example in Visual Basic .NET

Public Module StarsProgram
   Private Function Ask(prompt As String) As String
      Console.Write(prompt)
      Return Console.ReadLine()
   End Function

   Public Sub Main()
      Dim userName = Ask("What is your name: ")
      Console.WriteLine("Hello {0}", userName)

      Dim answer As String

      Do
         Dim numStars = CInt(Ask("How many stars do you want: "))
         Dim stars As New String("*"c, numStars)
         Console.WriteLine(stars)

         Do
            answer = Ask("Do you want more stars? ")
         Loop Until answer <> ""
      Loop While answer.StartsWith("Y", StringComparison.OrdinalIgnoreCase)

      Console.WriteLine("Goodbye {0}", userName)
   End Sub
End Module

Similar things will be used in C++, like if, then, for, do, while... which together define the program flow. You don't need to use goto to jump to the next statement. In specific cases you can still use goto if it makes the control flow clearer, but in general there's no need for it



来源:https://stackoverflow.com/questions/18035011/how-to-avoid-goto-in-c

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!