是否使用GOTO?

时间:2008-12-18 20:39:02

标签: c++ goto

目前我正在开展一个高度使用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;
}

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

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

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

哪种方式是对的?


来自Scott的评论:

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

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

35 个答案:

答案 0 :(得分:59)

我不确定清理代码是什么意思,但在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或类似的东西,因为上面的例子显然是不完美的。

答案 1 :(得分:59)

我从来没有在C ++中使用goto。永远。 EVER。如果有这种情况应该使用它,这是非常罕见的。如果你实际上正在考虑将goto作为逻辑的标准部分,那么某些东西已经飞离了轨道。

答案 2 :(得分:22)

关于gotos和你的代码,人们基本上有两点要点:

  1. Goto很糟糕。很难找到一个你需要的地方,但我不建议完全打击它。虽然C ++有足够聪明的控制流程来使goto很少适合。

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

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

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

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

答案 3 :(得分:20)

你的代码非常非惯用,你永远不应该写它。你基本上是在C ++中模拟C语言。但是其他人已经对此进行了评论,并指出RAII是另一种选择。

但是,您的代码无法正常工作,因为:

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

不会永远评估为true(除非您以奇怪的方式重载operator new)。如果operator new无法分配足够的内存,则会抛出异常,永远永远返回0,至少不会返回此组{参数;有一个特殊的placement-new重载,它接受类型为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;
}

答案 4 :(得分:16)

可能是not a good idea

答案 5 :(得分:12)

一般而言,从表面上看,如果您只有一个标签,那么您的方法没有任何问题,而且这些标签总是向前发展。例如,此代码:

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 = 1;
        goto exit;
    }
exit:
    delete pWhatEver;
    return ret;
}

彼此并非完全不同。如果你能接受另一个,你应该能够接受另一个。

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

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

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

比前两个示例更短,更易于阅读,也更易于维护。

所以,如果可以,我建议使用RAII方法。

答案 6 :(得分:8)

您的示例不是例外安全。

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

此时(抛出bad_alloc时),您的堆栈将被解开,在调用堆栈的路上缺少所有清理代码,因此不会清理代码。

你需要研究智能指针。在上面的情况中,您可以使用std::auto_ptr<>

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

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

答案 7 :(得分: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,更重要的是,找到那些不存在的那些。我需要跟踪函数的所有路径,以确保函数得到正确清理。这对我来说就像意大利面条代码。

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

答案 8 :(得分:6)

在我编程的八年里,我经常使用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仍然被大量使用的唯一情况是大型机汇编语言,我知道的程序员确保记录代码跳转的位置和原因。

答案 9 :(得分:6)

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

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

答案 10 :(得分:5)

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

答案 11 :(得分:5)

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

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

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

做编译器的工作。生成一个标签,其目的是跳过身体,而不是执行任何后续操作。即。

 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-伪装的是通过定义几个宏可以转化为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-in-confguise代码,因为它可以像未经伪装的getos一样陷入相同的意大利面条代码问题。

补充说:只是为了让人放心:我认为优秀程序员的一个标志就是承认共同规则何时不适用。

答案 12 :(得分: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。最后,有些人不会在他们的代码中使用异常并指示编译器禁用它们。因此,并非所有代码都必须是异常安全的。

答案 13 :(得分:5)

当“尾端逻辑”对于某些但不是全部的情况是常见的时,Goto提供更好的don't repeat yourself(DRY)。特别是在“切换”语句中,当一些交换分支具有尾端通用性时,我经常使用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,但是当异常时你经常会发生尾端 - 合并 - 不重复 - 自我 - 安全不是问题。

总之,虽然goto可用于编码类似意大利面条的结构,但是对于某些但不是全部情况通用的尾端序列,goto会提高代码的可读性。如果你本来可以复制/粘贴东西,那么甚至可维护性,以便稍后某人可能会更新一个而不是另一个。因此,另一个对教条狂热的案例可能适得其反。

答案 14 :(得分:4)

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

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

其次,正如你所指出的那样,它会迫使你在使用之前很久就声明变量,这将使得理解代码变得更加困难。

第三,虽然你提出了一个相当自律的goto使用,但是会有一种更宽松的方式使用它们的诱惑,然后代码将变得难以理解。

很少有goto适合的情况。大多数时候,当你想要使用它们时,这是一个你做错事的信号。

答案 15 :(得分:4)

由于这是一个经典主题,我将回复Dijkstra的Go-to statement considered harmful(最初在ACM上发表)。

答案 16 :(得分:3)

我在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级嵌套是我使用的地方转到。只有两个地方。

答案 17 :(得分:3)

很多人因为恶魔而感到害怕;他们不是。那说,你永远不需要一个;总有一种更好的方式。

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

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

这不是完美的,但它更容易理解,因为所有方法都会被命名以清楚地表明问题可能是什么。

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

答案 18 :(得分:3)

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

答案 19 :(得分: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有很多方面(即使用智能指针,堆栈,使用向量而不是可变长度数组等),但总的来说是编写尽可能少的代码,让编译器清理内容。正确的时刻。

答案 20 :(得分: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;
}

答案 21 :(得分:1)

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

答案 22 :(得分:1)

我认为使用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:;
}

答案 23 :(得分: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
   {
      ...
   }
}

答案 24 :(得分:1)

在C ++中使用GOTO标签是一种糟糕的编程方式,您可以通过 OO编程(解构器!)并尝试将程序保持为小来减少需求可能的。

您的示例看起来有点奇怪,无需删除空指针。现在,当指针无法分配时会抛出异常。

您的程序可以写成:

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块来处理内存不足的问题,告诉用户内存不足,这是非常罕见的 ......(操作系统本身不是吗?)也告诉这个?)

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

答案 25 :(得分:1)

该代码存在许多问题,其中大部分都已经指出,例如:

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

  • 当普通实例可能正常工作时使用指针。

  • 未利用STL类型,例如auto_ptr

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

我从来不需要goto,而且我总是发现使用goto是一系列问题的症状。你的案子似乎也不例外。

答案 26 :(得分:1)

我不会说goto总是坏的,但你肯定会使用它。这种“清理部分”在1990年代早期非常普遍,但将它用于新代码却是纯粹的邪恶。

答案 27 :(得分:0)

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

我可以从经验中说,对于可能需要维护代码的其他程序员来说,非常努力地遵循逻辑。我遇到了一个沉重的意大利面条代码和我的OO背景,这是调试和改变的噩梦。是的,这段代码也使用了goto的清理功能。不需要时非常沮丧。除非绝对必要,否则不要使用goto。

答案 28 :(得分:0)

以前的所有评论:

  1. goto非常糟糕
  2. 这使得代码难以阅读和理解。
  3. 可能引起众所周知的问题“意大利面条代码”
  4. 每个人都同意不应该这样做。
  5. 但我正在使用以下场景

    1. 以前只用一个标签。
    2. goto部分用于清理代码并设置返回值。 如果我不使用goto,那么我需要创建一个每种数据类型的类。就像我需要将int *包装成一个类。
    3. 整个项目都在遵循。
    4. 我同意这很糟糕,但如果遵循正确的话,它仍然会使事情变得更容易。

答案 29 :(得分:0)

  

Alien01 写道:   目前我正在开发一个大量使用goto语句的项目。 goto语句的主要目的是在例程中使用一个清理部分而不是多个return语句。

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

异常处理技术是一种错误处理逻辑,它与程序逻辑并行工作。这是一个更优雅的解决方案,因为它提供了这种分离,同时提供了将控制权移动到与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;
    }

答案 30 :(得分:0)

使用“GOTO”将改变程序的“逻辑”以及你如何输入或如何设想它将起作用。

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

然而,如果我们在Assmebly级别上看这个,那么“跳跃”就像使用GOTO一样,并且一直在使用,但是,在装配中你可以清除,你知道你在堆栈上有什么在你传递之前注册。

所以,当使用GOTO时,我会确保软件会“出现”,因为合作者会输入,GOTO会对你的软件产生“坏”影响。

因此,这更像是为什么不使用GOTO而不是替代解决方案的解释,因为这非常取决于其他所有内容的构建。

答案 31 :(得分: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;
  }

答案 32 :(得分: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;
}

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

答案 33 :(得分:0)

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

if / goto不会,也不需要删除p。用return false替换goto会产生相同的效果(然后你可以删除Exit标签)。

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

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

保罗。

答案 34 :(得分:-4)

我不知道关于goto的这个东西来自哪里......

在编译语言中,每个条件指令(ifswitchforwhile等)都在“cmp”“jmp”中解析J'-”在机器代码中(jmp IS goto)。

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

绝对没有理由不在C ++中使用GOTO:无论如何,生成的代码到处都是“jmp”。

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

反对goto的初始论据是代码更难检查。

这太荒谬了: 不会检查代码(或仅检查ONCE):它被执行,小而快。优化的代码片段必须避免冗余(通过重复使用相同的指令)并避免运行时检查(与硬件故障无关的异常应该在设计时避免,而不是在运行时)。