C++ 转到还是不转到?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/379172/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 15:02:39  来源:igfitidea点击:

To GOTO or not to GOTO?

c++goto

提问by anand

Currently I am working on a project where goto statements are heavely used. The main purpose of goto statements is to have one cleanup section in a routine rather than multiple return statements. Like below:

目前,我正在从事一个大量使用 goto 语句的项目。goto 语句的主要目的是在例程中包含一个清理部分,而不是多个 return 语句。如下图:

BOOL foo()
{
   BOOL bRetVal = FALSE;
   int *p = NULL;

   p = new int;
   if (p == NULL)
   {
     cout<<" OOM \n";
     goto Exit;
   }

   // Lot of code...

Exit:
   if(p)
   {
     delete p;
     p = NULL;
   }
   return bRetVal;
}

This makes it much easier as we can track our clean up code at one section in code, that is, after the Exit label.

这使事情变得更加容易,因为我们可以在代码中的一个部分(即 Exit 标签之后)跟踪我们的清理代码。

However, I have read many places it's bad practice to have goto statements.

但是,我在很多地方都读过 goto 语句是不好的做法。

Currently I am reading the Code Completebook, and it says that we need to use variables close to their declarations. If we use goto then we need to declare/initialize all variables before first use of goto otherwise the compiler will give errors that initialization of xx variable is skipped by the goto statement.

目前我正在阅读Code Complete一书,它说我们需要使用靠近它们声明的变量。如果我们使用 goto 那么我们需要在第一次使用 goto 之前声明/初始化所有变量,否则编译器会给出错误,说 xx 变量的初始化被 goto 语句跳过。

Which way is right?

哪种方式是对的?



From Scott's comment:

来自斯科特的评论:

It looks like using goto to jump from one section to another is bad as it makes the code hard to read and understand.

使用 goto 从一个部分跳转到另一个部分看起来很糟糕,因为它使代码难以阅读和理解。

But if we use goto just to go forward and to one label then it should be fine(?).

但是如果我们使用 goto 只是为了前进并转到一个标签,那么它应该没问题(?)。

采纳答案by Tamas Czinege

I am not sure what do you mean by clean up code but in C++ there is a concept called "resource acquisition is initialization" and it should be the responsibility of your destructors to clean up stuff.

我不确定清理代码是什么意思,但在 C++ 中有一个概念叫做“资源获取是初始化”,清理东西应该是你的析构函数的责任。

(Note that in C# and Java, this is usually solved by try/finally)

(注意,在 C# 和 Java 中,这通常是通过 try/finally 解决的)

For more info check out this page: http://www.research.att.com/~bs/bs_faq2.html#finally

有关更多信息,请查看此页面:http: //www.research.att.com/~bs/bs_faq2.html#finally

EDIT: Let me clear this up a little bit.

编辑:让我稍微澄清一下。

Consider the following code:

考虑以下代码:

void MyMethod()
{
    MyClass *myInstance = new MyClass("myParameter");
    /* Your code here */
    delete myInstance;
}

The problem: What happens if you have multiple exits from the function? You have to keep track of each exit and delete your objects at all possible exits! Otherwise, you will have memory leaks and zombie resources, right?

问题:如果您有多个退出函数会发生什么?您必须跟踪每个出口并在所有可能的出口处删除您的对象!否则,你会有内存泄漏和僵尸资源,对吧?

The solution: Use object references instead, as they get cleaned up automatically when the control leaves the scope.

解决方案:改用对象引用,因为它们会在控件离开作用域时自动清除。

void MyMethod()
{
    MyClass myInstance("myParameter");
    /* Your code here */
    /* You don't need delete - myInstance will be destructed and deleted
     * automatically on function exit */
}

Oh yes, and use std::unique_ptror something similar because the example above as it is is obviously imperfect.

哦,是的,使用std::unique_ptr或类似的东西,因为上面的例子显然是不完美的。

回答by Gene Roberts

I've never had to use a goto in C++. Ever. EVER. If there is a situation it should be used, it's incredibly rare. If you are actually considering making goto a standard part of your logic, something has flown off the tracks.

我从来没有在 C++ 中使用过 goto。曾经。曾经。如果有情况应该使用它,这是非常罕见的。如果你真的在考虑让 goto 成为你逻辑的标准部分,那么有些东西已经偏离轨道了。

回答by Brian

There are basically two points people are making in regards to gotos and your code:

关于 goto 和您的代码,人们基本上有两点:

  1. Goto is bad.It's very rare to encounter a place where you need gotos, but I wouldn't suggest striking it completely. Though C++ has smart enough control flow to make goto rarely appropriate.

  2. Your mechanism for cleanup is wrong:This point is far more important. In C, using memory management on your own is not only OK, but often the best way to do things. In C++, your goal should be to avoid memory management as much as possible. You should avoid memory management as much as possible. Let the compiler do it for you. Rather than using new, just declare variables. The only time you'll really need memory management is when you don't know the size of your data in advance. Even then, you should try to just use some of the STLcollections instead.

  1. 后藤不好。遇到需要 goto 的地方是非常罕见的,但我不建议完全删除它。尽管 C++ 具有足够智能的控制流,使得 goto 很少适用。

  2. 您的清理机制是错误的:这一点更为重要。在 C 中,自己使用内存管理不仅可以,而且通常是最好的做事方式。在 C++ 中,您的目标应该是尽可能避免内存管理。您应该尽可能避免内存管理。让编译器为你做。而不是使用new,只需声明变量。您真正需要内存管理的唯一时间是您事先不知道数据的大小。即便如此,您也应该尝试只使用一些STL集合。

In the event that you legitimately need memory management (you have not really provided any evidence of this), then you should encapsulate your memory management within a class via constructors to allocate memory and deconstructors to deallocate memory.

如果您合法地需要内存管理(您还没有真正提供任何证据),那么您应该通过构造函数将内存管理封装在一个类中以分配内存和解构函数以释放内存。

Your response that your way of doing things is much easier is not really true in the long run. Firstly, once you get a strong feel for C++ making such constructors will be 2nd nature. Personally, I find using constructors easier than using cleanup code, since I have no need to pay careful attention to make sure I am deallocating properly. Instead, I can just let the object leave scope and the language handles it for me. Also, maintaining them is MUCH easier than maintaining a cleanup section and much less prone to problems.

从长远来看,您认为自己的做事方式要容易得多的回答并不真实。首先,一旦你对 C++ 有了强烈的感觉,制作这样的构造函数将是第二自然。就我个人而言,我发现使用构造函数比使用清理代码更容易,因为我不需要特别注意以确保正确释放。相反,我可以让对象离开作用域,语言为我处理它。此外,维护它们比维护清理部分容易得多,也更不容易出现问题。

In short, gotomay be a good choice in some situations but not in this one. Here it's just short term laziness.

简而言之,goto在某些情况下可能是一个不错的选择,但在这种情况下则不然。这只是短期的懒惰。

回答by Konrad Rudolph

Your code is extremely non-idiomatic and you should never write it. You're basically emulating C in C++ there. But others have remarked on that, and pointed to RAII as the alternative.

你的代码非常不习惯,你永远不应该写它。你基本上是在那里用 C++ 模拟 C。但其他人对此发表了评论,并指出 RAII 作为替代方案。

However, your code won't workas you expect, because this:

但是,您的代码不会像您预期的那样工作,因为:

p = new int;
if(p==NULL) { … }

won't everevaluate to true(except if you've overloaded operator newin a weird way). If operator newis unable to allocate enough memory, it throws an exception, it never, everreturns 0, at least not with this set of parameters; there's a special placement-new overload that takes an instance of type std::nothrowand that indeed returns 0instead of throwing an exception. But this version is rarely used in normal code. Some low-level codes or embedded device applications could benefit from it in contexts where dealing with exceptions is too expensive.

不会永远计算为true(除非你已经超负荷operator new的不可思议的方式)。如果operator new无法分配足够的内存,它抛出一个异常,它永远永远的回报0,至少不与本组参数; 有一个特殊的placement-new 重载,它采用类型的实例,std::nothrow并且确实返回0而不是抛出异常。但是这个版本在普通代码中很少使用。在处理异常成本太高的情况下,一些低级代码或嵌入式设备应用程序可以从中受益。

Something similar is true for your deleteblock, as Harald as said: if (p)is unnecessary in front of delete p.

类似的情况也适用于您的delete区块,正如 Harald 所说:if (p)delete p.

Additionally, I'm not sure if your example was chose intentionally because this code can be rewritten as follows:

此外,我不确定您的示例是否是有意选择的,因为此代码可以重写如下:

bool foo() // prefer native types to BOOL, if possible
{
    bool ret = false;
    int i;
    // Lots of code.
    return ret;
}

回答by Marc Charbonneau

回答by Scott Wisniewski

In general, and on the surface, there isn't any thing wrong with your approach, provided that you only have one label, and that the gotos always go forward. For example, this code:

一般来说,从表面上看,您的方法没有任何问题,只要您只有一个标签,并且 goto 总是向前推进。例如,这段代码:

int foo()
{
    int *pWhatEver = ...;
    if (something(pWhatEver))
    { 
        delete pWhatEver;
        return 1;
    }
    else
    {
        delete pWhatEver;
        return 5;
    }
}

And this code:

而这段代码:

int foo()
{
    int ret;
    int *pWhatEver = ...;
    if (something(pWhatEver))
    { 
        ret = 1;
        goto exit;
    }
    else
    {
        ret = 1;
        goto exit;
    }
exit:
    delete pWhatEver;
    return ret;
}

really aren't all that different from each other. If you can accept one, you should be able to accept the other.

彼此之间真的没有什么不同。如果你能接受一个,你就应该能够接受另一个。

However, in many cases the RAII(resource acquisition is initialization) pattern can make the code much cleaner and more maintainable. For example, this code:

但是,在许多情况下,RAII(资源获取即初始化)模式可以使代码更简洁、更易于维护。例如,这段代码:

int foo()
{
    Auto<int> pWhatEver = ...;

    if (something(pWhatEver))
    {
        return 1;
    }
    else
    {
        return 5;
    }
}

is shorter, easier to read, and easier to maintain than both of the previous examples.

比前面两个示例更短、更易于阅读且更易于维护。

So, I would recommend using the RAII approach if you can.

因此,如果可以,我建议您使用 RAII 方法。

回答by Martin York

Your example is not exception safe.

你的例子不是异常安全的。

If you are using goto to clean up the code then, if an exception happens before the cleanup code, it is completely missed. If you claim that you do not use exceptions then you are mistaken because the newwill throw bad_alloc when it does not have enough memory.

如果您使用 goto 来清理代码,那么如果在清理代码之前发生异常,则完全错过。如果你声称你不使用异常,那么你就错了,因为new当它没有足够的内存时会抛出 bad_alloc 。

Also at this point (when bad_alloc is thrown), your stack will be unwound, missing all the cleanup code in every function on the way up the call stack thus not cleaning up your code.

同样在这一点上(当 bad_alloc 被抛出时),你的堆栈将被解开,在调用堆栈向上的过程中丢失每个函数中的所有清理代码,因此不会清理你的代码。

You need to look to do some research into smart pointers. In the situation above you could just use a std::auto_ptr<>.

您需要对智能指针进行一些研究。在上述情况下,您可以只使用std::auto_ptr<>.

Also note in C++ code there is no need to check if a pointer is NULL (usually because you never have RAW pointers), but because newwill not return NULL (it throws).

另请注意,在 C++ 代码中,无需检查指针是否为 NULL(通常是因为您从未拥有 RAW 指针),但因为new不会返回 NULL(抛出)。

Also in C++ unlike (C) it is common to see early returns in the code. This is because RAIIwill do the cleanup automatically, while in C code you need to make sure that you add special cleanup code at the end of the function (a bit like your code).

同样在 C++ 中,与 (C) 不同,在代码中看到早期返回是很常见的。这是因为RAII会自动进行清理,而在 C 代码中,您需要确保在函数末尾添加特殊的清理代码(有点像您的代码)。

回答by jalf

I think other answers (and their comments) have covered all the important points, but here's one thing that hasn't been done properly yet:

我认为其他答案(以及他们的评论)已经涵盖了所有要点,但还有一件事情还没有正确完成:

What your code should look like instead:

你的代码应该是这样的:

bool foo() //lowercase bool is a built-in C++ type. Use it if you're writing C++.
{
  try {
    std::unique_ptr<int> p(new int);
    // lots of code, and just return true or false directly when you're done
  }
  catch (std::bad_alloc){ // new throws an exception on OOM, it doesn't return NULL
    cout<<" OOM \n";
    return false;
  }
}

Well, it's shorter, and as far as I can see, more correct (handles the OOM case properly), and most importantly, I didn't need to write any cleanup code or do anything special to "make sure my return value is initialized".

嗯,它更短,据我所知,更正确(正确处理 OOM 情况),最重要的是,我不需要编写任何清理代码或做任何特殊的事情来“确保我的返回值被初始化”。

One problem with your code I only really noticed when I wrote this, is "what the hell is bRetVal's value at this point?". I don't know because, it was declared waaaaay above, and it was last assigned to when? At some point above this. I have to read through the entire function to make sure I understand what's going to be returned.

我在编写此代码时才真正注意到您的代码的一个问题是“此时 bRetVal 的值到底是什么?”。我不知道,因为它在上面被声明为 waaaaay,它最后被分配到什么时候?在此之上的某个点。我必须通读整个函数以确保我了解将要返回的内容。

And how do I convince myself that the memory gets freed?

我如何说服自己内存被释放?

How do I knowthat we never forget to jump to the cleanup label? I have to work backwards from the cleanup label, finding everygoto that points to it, and more importantly, find the ones that aren't there. I need to trace through all paths of the function just to be sure that the function gets cleaned up properly. That reads like spaghetti code to me.

我怎么知道我们永远不会忘记跳转到清理标签?我必须从清理标签开始向后工作,找到指向它的每个转到,更重要的是,找到不存在的那些。我需要跟踪该函数的所有路径,以确保该函数得到正确清理。这对我来说就像意大利面条式的代码。

Very fragile code, because everytime a resource has to be cleaned up you have to rememberto duplicate your cleanup code. Why not write it once, in the type that needs to be cleaned up? And then rely on it being executed automatically, every time we need it?

非常脆弱的代码,因为每次必须清理资源时,您都必须记住复制清理代码。为什么不写一次,在需要清理的类型中?然后依靠它自动执行,每次我们需要它?

回答by Jared

In the eight years I've been programming I've used goto a lot, most of that was in the first year when I was using a version of GW-BASICand a book from 1980 that didn't make it clear goto should only be used in certain cases. The only time I've used goto in C++ is when I had code like the following, and I'm not sure if there was a better way.

在我编程的八年里,我经常使用 goto,其中大部分是在第一年我使用GW-BASIC 的一个版本和 1980 年的一本书没有明确说明 goto 应该只在某些情况下使用。我唯一一次在 C++ 中使用 goto 是当我有如下代码时,我不确定是否有更好的方法。

for (int i=0; i<10; i++) {
    for (int j=0; j<10; j++)
    {
        if (somecondition==true)
        {
            goto finish;
        }
        //Some code
    }
    //Some code
}
finish:

The only situation I know of where goto is still used heavily is mainframe assembly language, and the programmers I know make sure to document where code is jumping and why.

我知道的唯一一种仍然大量使用 goto 的情况是大型机汇编语言,我认识的程序员确保记录代码跳转的位置以及原因。

回答by too much php

You should read this thread summary from the Linux kernel mailing lists (paying special attention to the responses from Linus Torvalds) before you form a policy for goto:

在为goto以下内容制定政策之前,您应该从 Linux 内核邮件列表中阅读此主题摘要(特别注意来自 Linus Torvalds 的回复):

http://kerneltrap.org/node/553/2131

http://kerneltrap.org/node/553/2131