63

目前我正在研究一个大量使用 goto 语句的项目。goto 语句的主要目的是在例程中有一个清理部分,而不是多个返回语句。如下所示:

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;
}

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

但是,我已经阅读了很多地方,使用 goto 语句是不好的做法。

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

哪种方式是正确的?


来自斯科特的评论:

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

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

4

35 回答 35

60

我不确定清理代码是什么意思,但在 C++ 中有一个名为“资源获取就是初始化”的概念,你的析构函数应该负责清理内容。

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

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

编辑:让我澄清一下。

考虑以下代码:

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

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

解决方案:改用对象引用,因为当控件离开范围时它们会自动清理。

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

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

于 2008-12-18T20:43:09.100 回答
60

我从来不用在 C++ 中使用 goto。曾经。曾经。如果有一种情况应该使用它,那是非常罕见的。如果您实际上正在考虑将 goto 作为逻辑的标准部分,那么有些东西已经偏离轨道。

于 2008-12-18T21:03:08.827 回答
22

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

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

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

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

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

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

于 2008-12-18T21:46:46.693 回答
20

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

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

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

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

delete正如 Harald 所说,您的街区也有类似的情况:if (p)delete p.

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

bool foo() // prefer native types to BOOL, if possible
{
    bool ret = false;
    int i;
    // Lots of code.
    return ret;
}
于 2008-12-18T20:58:43.670 回答
17

可能不是一个好主意

于 2008-12-18T20:53:10.267 回答
11

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

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

而这段代码:

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

真的没有什么不同。如果你能接受一个,你应该能接受另一个。

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

int foo()
{
    Auto<int> pWhatEver = ...;
    
    if (something(pWhatEver))
    {
        return 1;
    }
    else
    {
        return 5;
    }
}

比前面的两个例子更短、更容易阅读、更容易维护。

所以,如果可以的话,我会推荐使用 RAII 方法。

于 2008-12-18T21:07:20.737 回答
9

您的示例不是异常安全的。

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

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

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

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

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

于 2008-12-18T22:04:58.640 回答
8

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

你的代码应该是这样的:

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;
  }
}

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

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

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

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

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

于 2008-12-18T22:03:52.697 回答
7

在我编程的八年里,我经常使用 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:

我所知道的仍然大量使用 goto 的唯一情况是大型机汇编语言,我认识的程序员确保记录代码在哪里跳转以及为什么。

于 2008-12-22T20:29:41.460 回答
6

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

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

于 2008-12-19T02:36:41.943 回答
5

一般来说,您应该设计您的程序以限制对 goto 的需求。使用 OO 技术“清理”您的返回值。有一些方法可以做到这一点,不需要使用 goto 或使代码复杂化。在某些情况下 goto 非常有用(例如,深度嵌套的作用域),但如果可能的话应该避免使用。

于 2008-12-18T20:43:06.637 回答
5

正如在 Linux 内核中使用的那样,当单个函数必须执行 2 个或更多可能需要撤消的步骤时,用于清理的 goto 可以很好地工作。步骤不必是内存分配。它可能是对一段代码或 I/O 芯片组寄存器的配置更改。Goto 应该只在少数情况下需要,但通常如果使用得当,它们可能是最好的解决方案。他们不是邪恶的。它们是一种工具。

代替...

do_step1;
if (failed)
{
  undo_step1;
  return failure;
}

do_step2;
if (failed)
{
  undo_step2;
  undo_step1;
  return failure;
}

do_step3;
if (failed)
{
  undo_step3;
  undo_step2;
  undo_step1;
  return failure;
}

return success;

您可以对 goto 语句执行相同的操作,如下所示:

do_step1;
if (failed) goto unwind_step1;

do_step2;
if (failed) goto unwind_step2;

do_step3;
if (failed) goto unwind_step3;

return success;

unwind_step3:
  undo_step3;

unwind_step2:
  undo_step2;

unwind_step1:
  undo_step1;

return failure;

应该清楚的是,鉴于这两个示例,一个比另一个更可取。至于 RAII 人群……只要他们能保证展开总是以完全相反的顺序发生:3、2、1,这种方法没有任何问题。最后,有些人不在他们的代码中使用异常并指示编译器禁用它们。因此,并非所有代码都必须是异常安全的。

于 2008-12-22T19:00:17.490 回答
5

GOTO 的缺点已得到很好的讨论。我只想补充一点:1)有时你必须使用它们并且应该知道如何最小化问题,2)一些公认的编程技术是伪装的,所以要小心。

1) 当您必须使用 GOTO 时,例如在 ASM 或 .bat 文件中,请像编译器一样思考。如果你想编码

 if (some_test){
  ... the body ...
}

做编译器所做的事情。生成一个标签,其目的是跳过正文,而不是执行任何后续操作。IE

 if (not some_test) GOTO label_at_end_of_body
  ... the body ...
label_at_end_of_body:

不是

 if (not some_test) GOTO the_label_named_for_whatever_gets_done_next
  ... the body ...

the_label_named_for_whatever_gets_done_next:

换句话说,标签的目的不是做某事,而是跳过某事。

2) 我所说的 GOTO-in-disguise 是只要定义几个宏就可以变成 GOTO+LABELS 代码的任何东西。一个例子是通过具有状态变量和 while-switch 语句来实现有限状态自动机的技术。

 while (not_done){
    switch(state){
        case S1:
            ... do stuff 1 ...
            state = S2;
            break;
        case S2:
            ... do stuff 2 ...
            state = S1;
            break;
        .........
    }
}

可以变成:

 while (not_done){
    switch(state){
        LABEL(S1):
            ... do stuff 1 ...
            GOTO(S2);
        LABEL(S2):
            ... do stuff 2 ...
            GOTO(S1);
        .........
    }
}

只需定义几个宏。几乎任何 FSA 都可以转换为结构化的无 goto 代码。我更喜欢远离 GOTO-in-disguise 代码,因为它可能会陷入与未加掩饰的 goto 相同的意大利面条代码问题。

补充:只是为了保证:我认为优秀程序员的一个标志是认识到通用规则不适用。

于 2008-12-22T19:46:56.897 回答
4

使用 goto 去清理部分会导致很多问题。

首先,清理部分容易出现问题。它们具有低内聚性(没有真正的角色可以用程序试图做什么来描述),高耦合(正确性非常依赖于其他代码部分),并且根本不是异常安全的。看看是否可以使用析构函数进行清理。例如,如果int *p更改为auto_ptr<int> p,则 p 指向的内容将被自动释放。

其次,正如您所指出的,这将迫使您在使用之前很久就声明变量,这将使代码更难理解。

第三,当您建议对 goto 进行相当规范的使用时,会很容易以更宽松的方式使用它们,这样代码就会变得难以理解。

很少有适合 goto 的情况。大多数时候,当你想使用它们时,这表明你做错了。

于 2008-12-18T20:48:44.923 回答
4

由于这是一个经典话题,我将回复 Dijkstra 的Go-to 声明被认为有害(最初发表在 ACM 中)。

于 2008-12-18T21:13:06.467 回答
4

当“尾端逻辑”在某些但并非所有情况下都很常见时,Goto 提供了更好的不要重复自己(DRY)。尤其是在“switch”语句中,当某些 switch 分支具有尾端通用性时,我经常使用 goto。

switch(){
   case a:  ... goto L_abTail;
   case b: ... goto L_abTail;
L_abTail: <commmon stuff>
    break://end of case b
case c:
.....
}//switch

您可能已经注意到,当您需要在例程中间进行这种尾端合并时,引入额外的花括号就足以满足编译器的要求。换句话说,您不需要在顶部声明所有内容;这确实是较差的可读性。

...
   goto L_skipMiddle;
{
    int declInMiddleVar = 0;
    ....
}
L_skipMiddle: ;

随着更高版本的Visual Studio检测到未初始化变量的使用,我发现自己总是初始化大多数变量,即使我认为它们可能会在所有分支中分配 - 编写一个引用从未分配的变量的“跟踪”语句很容易因为您的大脑并不认为跟踪语句是“真实代码”,但当然 Visual Studio 仍会检测到错误。

除了不要重复自己之外,为这种尾部逻辑分配标签名称甚至似乎通过选择好的标签名称来帮助我保持头脑清醒。如果没有有意义的标签,您的评论最终可能会说同样的话。

当然,如果您实际上是在分配资源,那么如果 auto-ptr 不适合,您确实必须使用 try-catch,但是当异常安全时,tail-end-merge-don't-repeat-yourself 经常发生没什么大不了。

总而言之,虽然 goto 可用于编写类似意大利面条的结构,但对于某些但并非所有情况常见的尾端序列,goto 提高了代码的可读性甚至可维护性如果您否则会复制/粘贴内容,以便稍后有人可能会更新一个而不是另一个。所以这是另一种情况,对教条狂热可能会适得其反。

于 2008-12-19T06:49:32.543 回答
4

我在 C++ 代码中使用 goto 的唯一两个原因是:

  • 打破 2 级以上的嵌套循环
  • 像这样的复杂流程(我的程序中的评论):

    /* Analysis algorithm:
    
      1.  if classData [exporter] [classDef with name 'className'] exists, return it,
          else
      2.    if project/target_codename/temp/classmeta/className.xml exist, parse it and go back to 1 as it will succeed.
      3.    if that file don't exists, generate it via haxe -xml, and go back to 1 as it will succeed.
    
    */
    

这里为了代码可读性,在这个注释之后,我定义了step1标签,并在第2步和第3步中使用了它。实际上,在60多个源文件中,只有这种情况和嵌套的一个4层是我使用goto的地方。只有两个地方。

于 2013-04-22T00:50:29.753 回答
3

C 中每个函数都有一个单一的出口点习语的全部目的是将所有清理内容放在一个地方。如果您使用 C++ 析构函数来处理清理,则不再需要 - 无论函数有多少退出点,清理都将完成。因此,在设计合理的 C++ 代码中,不再需要这种东西。

于 2008-12-18T21:01:31.153 回答
3

很多人都被 gotos 吓坏了。他们不是。也就是说,你永远不需要一个;几乎总是有更好的方法。

当我发现自己“需要”一个 goto 来做这类事情时,我几乎总是发现我的代码太复杂了,很容易分解成几个更容易阅读和处理的方法调用。您的调用代码可以执行以下操作:

// Setup
if(
     methodA() &&
     methodB() &&
     methodC()
 )
 // Cleanup

并不是说这很完美,但它更容易遵循,因为您的所有方法都将被命名以清楚地表明问题可能是什么。

然而,通读评论应该表明您的团队有比 goto 处理更紧迫的问题。

于 2008-12-19T01:29:50.957 回答
2

您给我们的代码(几乎)是写在 C++ 文件中的 C 代码。在不使用 C++ 代码/库的 C 程序中,您使用的内存清理类型是可以的。

在 C++ 中,您的代码根本不安全且不可靠。在 C++ 中,您要求的管理方式有所不同。使用构造函数/析构函数。使用智能指针。使用堆栈。总之,使用RAII

您的代码可以(即,在 C++ 中,应该)写成:

BOOL foo()
{
   BOOL bRetVal = FALSE;

   std::auto_ptr<int> p = new int;

   // Lot of code...

   return bRetVal ;
}

(请注意,在实际代码中新建一个 int 有点愚蠢,但您可以将 int 替换为任何类型的对象,这样就更有意义了)。假设我们有一个 T 类型的对象(T 可以是一个 int、某个 C++ 类等)。那么代码就变成了:

BOOL foo()
{
   BOOL bRetVal = FALSE;

   std::auto_ptr<T> p = new T;

   // Lot of code...

   return bRetVal ;
}

或者更好的是,使用堆栈:

BOOL foo()
{
   BOOL bRetVal = FALSE;

   T p ;

   // Lot of code...

   return bRetVal;
}

无论如何,上述任何示例都比您的示例更易于阅读和安全。

RAII 有很多方面(即使用智能指针、堆栈、使用向量而不是可变长度数组等),但总而言之,就是尽可能少地编写代码,让编译器在适当的时候清理这些东西。

于 2008-12-20T17:11:48.430 回答
1

以上所有内容都是有效的,您可能还想看看是否可以通过减少标记为“大量代码”的部分中的代码量来降低代码的复杂性并减轻对 goto 的需求在你的例子中。Additionalydelete 0是一个有效的 C++ 语句

于 2008-12-18T20:55:12.087 回答
1

在 C++ 中使用 GOTO 标签是一种不好的编程方式,您可以通过执行OO 编程(解构函数!)并尝试使过程尽可能小来减少需求。

你的例子看起来有点奇怪,没有必要删除 NULL 指针。现在,当指针无法分配时会引发异常。

你的程序可以写成这样:

bool foo()
{
    bool bRetVal = false;
    int p = 0;

    // Calls to various methods that do algorithms on the p integer
    // and give a return value back to this procedure.

    return bRetVal;
}

您应该在处理内存不足问题的主程序中放置一个 try catch 块,通知用户内存不足,这是非常罕见的......(操作系统本身也不会通知这一点吗?)

另请注意,并不总是需要使用指针,它们仅对动态事物有用。(在方法中创建不依赖于任何地方的输入的东西并不是真正动态的)

于 2008-12-18T21:06:24.747 回答
1

我不会说这goto总是不好的,但你对它的使用肯定是。这种“清理部分”在 1990 年代初期相当普遍,但将其用于新代码是纯粹的邪恶。

于 2008-12-18T21:41:09.233 回答
1

避免您在这里所做的最简单的方法是将所有这些清理工作放入某种简单的结构中并创建它的一个实例。例如,而不是:

void MyClass::myFunction()
{
   A* a = new A;
   B* b = new B;
   C* c = new C;
   StartSomeBackgroundTask();
   MaybeBeginAnUndoBlockToo();

   if ( ... )
   {
     goto Exit;
   }

   if ( ... ) { .. }
   else
   {
      ... // what happens if this throws an exception??? too bad...
      goto Exit;
   }

Exit:
  delete a;
  delete b;
  delete c;
  StopMyBackgroundTask();
  EndMyUndoBlock();
}

您应该以某种方式进行此清理,例如:

struct MyFunctionResourceGuard
{
  MyFunctionResourceGuard( MyClass& owner ) 
  : m_owner( owner )
  , _a( new A )
  , _b( new B )
  , _c( new C )
  {
      m_owner.StartSomeBackgroundTask();
      m_owner.MaybeBeginAnUndoBlockToo();
  }

  ~MyFunctionResourceGuard()
  {
     m_owner.StopMyBackgroundTask();
     m_owner.EndMyUndoBlock();
  }

  std::auto_ptr<A> _a;
  std::auto_ptr<B> _b;
  std::auto_ptr<C> _c;

};

void MyClass::myFunction()
{
   MyFunctionResourceGuard guard( *this );

   if ( ... )
   {
     return;
   }

   if ( ... ) { .. }
   else
   {
      ...
   }
}
于 2008-12-18T21:46:59.597 回答
1

该代码有很多问题,其中大部分已经被指出,例如:

  • 函数过长;将一些代码重构为单独的函数可能会有所帮助。

  • 在正常实例时使用指针可能会正常工作。

  • 不利用STL类型,例如 auto_ptr

  • 错误地检查错误,而不是捕获异常。(我认为在绝大多数平台上检查 OOM 是没有意义的,因为如果你的内存用完,你会遇到比你的软件可以解决的更大的问题,除非你正在编写操作系统本身)

我从来不需要 goto,而且我一直发现使用 goto 是一系列更大问题的症状。你的情况似乎也不例外。

于 2008-12-19T01:55:49.343 回答
1

几年前,我想出了一个避免 goto 的伪习语,与在 C 中进行异常处理有点相似。它可能已经被其他人发明了,所以我想我“独立发现了它”:)

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

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

       // Lot of code...

       bRetVal = TRUE;

    } while (false);

   if(p)
   {
     delete p;
     p= NULL;
   }

   return bRetVal;
}
于 2008-12-20T14:56:56.487 回答
1

我认为将 goto 用于退出代码是不好的,因为还有许多其他开销较低的解决方案,例如具有退出函数并在需要时返回退出函数值。但通常在成员函数中,这不应该是必需的,否则这可能表明发生了太多的代码膨胀。

通常,我在编程时对“no goto”规则所做的唯一例外是在将嵌套循环中断到特定级别时,我只是在进行数学编程时才遇到需要这样做。

例如:

for(int i_index = start_index; i_index >= 0; --i_index)
{
    for(int j_index = start_index; j_index >=0; --j_index)
        for(int k_index = start_index; k_index >= 0; --k_index)
            if(my_condition)
                goto BREAK_NESTED_LOOP_j_index;
BREAK_NESTED_LOOP_j_index:;
}
于 2012-08-11T22:30:33.057 回答
0

试试这种方式:

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

   p = new int;
   if (p == NULL)
   {
     cout<<" OOM \n";
   }
   else
   {
       // Lot of code...
   }

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

在“大量代码”部分中,“大量代码”很好地表明您可能应该将此部分重构为一个或多个方法或函数。

于 2008-12-18T20:59:18.757 回答
0

使用“GOTO”将改变程序的“逻辑”以及您的输入方式或您想象它的工作方式。

避免 GOTO 命令一直对我有用,所以当你认为你可能需要它时,你可能需要重新设计。

但是,如果我们在汇编级别上看这个,判断“跳转”就像使用 GOTO 并且一直在使用,但是,在汇编中,您可以清除您所知道的堆栈和其他寄存器中的内容传下去。

因此,在使用 GOTO 时,我会确保软件会“出现”,因为 co-coders 会进入,GOTO 会对您的软件产生“不良”影响。

因此,这更多地解释了为什么不使用 GOTO 而不是替代解决方案,因为这在很大程度上取决于其他一切的构建方式。

于 2008-12-18T21:02:30.467 回答
0

前面的评论都是不使用 goto 的好理由。

我可以根据经验说,对于可能需要维护您的代码 goto 的其他程序员来说,遵循逻辑非常困难。我遇到了繁重的 goto 意大利面条代码的情况,并且以我的 OO 背景为背景,调试和进行更改是一场噩梦。是的,这段代码也将 goto 用于清理功能。不需要的时候非常沮丧。除非绝对必要,否则不要使用 goto。

于 2008-12-18T21:17:27.970 回答
0

从之前的所有评论中:

  1. goto 非常非常糟糕
  2. 它使代码难以阅读和理解。
  3. 它可能导致众所周知的问题“意大利面条代码”
  4. 每个人都同意不应该这样做。

但我在以下场景中使用

  1. 它用于前进并且仅适用于一个标签。
  2. goto 部分用于清理代码并设置返回值。如果我不使用 goto,那么我需要为每种数据类型创建一个类。就像我需要将 int * 包装到一个类中一样。
  3. 整个项目都在遵循它。

我同意这很糟糕,但如果遵循得当,它仍然会让事情变得容易得多。

于 2008-12-18T21:33:07.953 回答
0

我可能错过了一些东西:如果 P 为空,则跳转到标签 Exit,然后测试它是否不为空(不是),看看是否需要删除它(这不是必需的,因为它从未分配过第一名)。

if/goto 不会,也不需要删除 p。将 goto 替换为 return false 将具有相同的效果(然后您可以删除 Exit 标签)。

我知道 goto 唯一有用的地方是深埋在令人讨厌的解析器(或词法分析器)中,以及伪造状态机(埋在大量 CPP 宏中)。在这两种情况下,它们被用来使非常扭曲的逻辑变得更简单,但这种情况非常罕见。

函数(A 调用 A')、Try/Catches 和 setjmp/longjmps 都是避免困难语法问题的更好方法。

保罗。

于 2008-12-18T23:19:31.017 回答
0

忽略 new 永远不会返回 NULL 的事实,请使用您的代码:

  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;
  }

并这样写:

  BOOL foo()
  {
     BOOL bRetVal = FALSE;

     int *p = new int;

     if (p!=NULL)
     {
        // Lot of code...

        delete p;
     }
     else
     {
        cout<<" OOM \n";
     }

     return bRetVal;
  }
于 2008-12-18T23:38:07.360 回答
0

Alien01写道: 目前,我正在研究一个大量使用 goto 语句的项目。goto 语句的主要目的是在例程中有一个清理部分,而不是多个返回语句。

换句话说,您希望将程序逻辑与简单重复乏味的例程分开,例如释放可能保留在不同代码位置的资源。

异常处理技术是一种与程序逻辑并行工作的错误处理逻辑。这是一个更优雅的解决方案,因为它提供了这样的分离,同时提供了将控制移动到其他代码块的能力,就像goto语句一样,所以我修改了你的脚本,如下所示:

class auxNullPtrException : public std::exception {
    public:
        auxNullPtrException::auxNullPtrException()
            : std::exception( " OOM \n") {}
    };

    BOOL foo()
    {
        BOOL bRetVal = FALSE;
        try {
            int *p = NULL;
            p = new int;
            if (p == NULL)
            {
                throw auxNullPtrException();
            }
            // Lot of code...
         }
         catch(auxNullPtrException & _auxNullPtrException)
         {
             std::cerr<<_auxNullPtrException.what();
             if(p)
             {
                 delete p;
                 p = NULL;
             }
         }
         return bRetVal;
    }
于 2008-12-21T00:32:44.200 回答
-4

我不知道goto这个东西是从哪里来的......

在编译语言中,每条条件指令(ifswitchforwhile等)都解析为“cmp”、“jmp”或“j??” 在机器代码中(jmp IS goto)。

事实上,一个非常优化的代码知道最佳执行路径,因此必须使用“goto”......使用 goto 编写线性代码比不使用堆栈时的方法和调用(“call” =“推”+“jmp”)。

绝对没有充分的理由不应该在 C++ 中使用 GOTO:生成的代码到处都是“jmp”。

这只是脚本中的一个问题(通常不可用),因为当 goto 指令存在时,goto 的目的地不一定被解释。

反对 goto 的最初论点是代码更难检查。

这很荒谬:代码不是用来检查的(或只检查一次):它是用来执行的,要小而快。一段优化的代码必须避免冗余(通过重用相同的指令)并避免运行时检查(与硬件故障无关的异常都应该在设计时避免,而不是在运行时)。

于 2012-09-11T16:01:55.100 回答