web-dev-qa-db-ja.com

GOTOを使用するかどうか

現在、gotoステートメントが頻繁に使用されるプロジェクトに取り組んでいます。 gotoステートメントの主な目的は、複数のreturnステートメントではなく、ルーチン内に1つのクリーンアップセクションを持つことです。以下のように:

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

これにより、コードの1つのセクション、つまりExitラベルの後にクリーンアップコードを追跡できるため、はるかに簡単になります。

ただし、gotoステートメントを使用するのは悪い習慣です。

現在、Code Completeの本を読んでいますが、宣言の近くで変数を使用する必要があると書かれています。 gotoを使用する場合、gotoを最初に使用する前にすべての変数を宣言/初期化する必要があります。そうしないと、コンパイラはxx変数の初期化がgotoステートメントによってスキップされるというエラーを出します。

どっちが正しい?


スコットのコメントから:

Gotoを使用してあるセクションから別のセクションにジャンプすることは、コードを読みやすく理解しにくくするため、悪いように見えます。

しかし、gotoを使用して1つのラベルに進む場合は、問題ありません(?)。

60
anand

コードをクリーンアップすることの意味はわかりませんが、C++には「resource Acquisition is initialization」という概念があり、デストラクタがクリーンアップする責任があります。もの。

(C#とJavaでは、これは通常try/finallyによって解決されることに注意してください)

詳細については、このページをご覧ください: http://www.research.att.com/~bs/bs_faq2.html#finally

[〜#〜] edit [〜#〜]:これを少しクリアしてみましょう。

次のコードを検討してください。

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

問題:関数から複数の出口がある場合はどうなりますか?各出口を追跡し、すべての可能な出口でオブジェクトを削除する必要があります!そうしないと、メモリリークとゾンビリソースが発生します。

Thesolution:代わりにオブジェクト参照を使用します。コントロールがスコープを離れると自動的にクリーンアップされるためです。

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 または同様のもの。上記の例は明らかに不完全であるためです。

60
Tamas Czinege

C++でgotoを使用する必要はありませんでした。今まで。今まで。使用すべき状況がある場合、それは非常にまれです。 gotoをロジックの標準部分にすることを実際に検討している場合は、何かが軌道に乗っています。

59
Gene Roberts

Gotoとあなたのコードに関して、基本的に2つのポイントがあります。

  1. 後藤は悪いです。 gotoが必要な場所に出くわすことは非常にまれですが、完全に打つことはお勧めしません。 C++には、gotoを適切に使用できないほどスマートな制御フローがありますが。

  2. クリーンアップのメカニズムが間違っています:この点ははるかに重要です。 Cでは、メモリ管理を自分で使用することは問題ないだけでなく、多くの場合、最善の方法です。 C++での目標は、メモリ管理を可能な限り避けることです。可能な限りメモリ管理を避ける必要があります。コンパイラに任せてください。 newを使用するのではなく、変数を宣言するだけです。本当にメモリ管理が必要になるのは、データのサイズが事前にわからないときだけです。それでも、代わりにSTLコレクションの一部を使用するようにしてください。

メモリ管理を正当に必要とする場合(実際にこの証拠を提供していない場合)、コンストラクタを介してメモリ管理をクラス内にカプセル化し、メモリを割り当て、デコンストラクタを使用してメモリの割り当てを解除する必要があります。

あなたの物事のやり方がずっと簡単だというあなたの反応は、長期的には本当ではありません。まず、C++の作成に強い感触を覚えたら、そのようなコンストラクターを作成するのは2番目の性質です。個人的には、クリーンアップコードを使用するよりもコンストラクタを使用する方が簡単です。適切に割り当てを解除するために注意を払う必要がないからです。代わりに、オブジェクトがスコープを離れるのを許可し、言語がオブジェクトを処理します。また、それらを保守することは、クリーンアップセクションを保守するよりもはるかに簡単で、問題が発生する可能性はずっと低くなります。

要するに、gotoはある状況では良い選択かもしれませんが、この状況ではそうではありません。ここでは、短期的な怠inessです。

22
Brian

あなたのコードは非常に非慣用的であり、決して書くべきではありません。基本的にはC++でCをエミュレートしています。しかし、他の人はそれについてコメントし、RAIIを代替案として指摘しました。

ただし、コード動作しません期待どおり、これは:

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

evertrueに評価しません(奇妙な方法でoperator newをオーバーロードした場合を除く)。 operator newが十分なメモリを割り当てることができない場合、例外をスローし、nevereverは少なくとも0を返します。パラメータのセット。タイプstd::nothrowのインスタンスを受け取り、例外をスローする代わりに0を実際に返す特別な配置の新しいオーバーロードがあります。しかし、このバージョンは通常のコードではめったに使用されません。いくつかの低レベルのコードまたは組み込みデバイスアプリケーションは、例外の処理が高すぎるコンテキストでこの恩恵を受けることができます。

Haraldが言ったように、deleteブロックにも同様のことが当てはまります。delete pの前ではif (p)は不要です。

さらに、このコードは次のように書き直すことができるため、あなたの例が意図的に選択されたかどうかはわかりません。

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

一般的に、表面的には、ラベルが1つだけであり、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 = 1;
        goto exit;
    }
exit:
    delete pWhatEver;
    return ret;
}

互いにそれほど違いはありません。一方を受け入れることができれば、もう一方を受け入れることができるはずです。

ただし、多くの場合、 [〜#〜] raii [〜#〜] (リソースの取得は初期化)パターンにより、コードがよりクリーンで保守しやすくなります。たとえば、次のコード:

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

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

前の両方の例よりも短く、読みやすく、保守しやすいです。

したがって、可能であればRAIIアプローチを使用することをお勧めします。

12

他の回答(およびそのコメント)がすべての重要なポイントをカバーしたと思いますが、まだ適切に行われていないことを1つ示します。

代わりに、コードは次のようになります。

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ケースを適切に処理します)そして最も重要なことには、クリーンアップコードを書く必要も特別なこともせずに「戻り値が初期化されていることを確認する」 「。

これを書いたときに気付いたコードの問題の1つは、「この時点でbRetValの値は一体何なのか?」です。わからない、それは上でwaaaaayと宣言され、いつ最後に割り当てられたのか?この上のある時点で。何が返されるかを理解するために、関数全体を読み通す必要があります。

そして、メモリが解放されることをどのように確信させるのですか?

どうすればknowクリーンアップラベルにジャンプすることを決して忘れないということですか? every gotoを見つけてクリーンアップラベルから逆方向に作業し、さらに重要なことに、存在しないものを見つけなければなりません。関数が適切にクリーンアップされることを確認するために、関数のすべてのパスをトレースする必要があります。私にはスパゲッティコードのように見えます。

everyリソースをクリーンアップする必要があるため、クリーンアップコードを複製するにはrememberを実行する必要があるため、非常に脆弱なコードです。クリーンアップが必要なタイプで一度書いてみませんか?そして、それが必要になるたびに、自動的に実行されることに依存していますか?

8
jalf

あなたの例は例外安全ではありません。

Gotoを使用してコードをクリーンアップしている場合、クリーンアップコードの前に例外が発生すると、完全に見逃されます。例外を使用しないと主張する場合、newは十分なメモリがないときにbad_allocをスローするため、間違えられます。

また、この時点(bad_allocがスローされたとき)で、スタックは巻き戻され、呼び出しスタックの途中ですべての関数のすべてのクリーンアップコードが失われるため、コードがクリーンアップされません。

スマートポインターの調査を行う必要があります。上記の状況では、std::auto_ptr<>

また、C++コードでは、ポインターがNULLかどうかを確認する必要はありません(通常RAWポインターがないため)が、newはNULLを返さない(スローする)ためです。

また(C)とは異なり、C++では、コード内で早期にリターンが発生するのが一般的です。これは、 [〜#〜] raii [〜#〜] が自動的にクリーンアップを行うのに対し、Cコードでは、必ず追加する必要があるためです。関数の最後の特別なクリーンアップコード(コードに少し似ています)。

8
Martin York

gotoのポリシーを作成する前に、Linuxカーネルのメーリングリスト(Linus Torvaldsからの応答に特に注意を払う)からこのスレッドの概要を読む必要があります。

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

6
too much php

8年間、私はよくgotoを使用してプログラミングを行ってきましたが、そのほとんどは GW-BASIC のバージョンと1980年の本を使用していた最初の1年でした。 t 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がまだ頻繁に使用されていることを知っている唯一の状況は、メインフレームアセンブリ言語です。私が知っているプログラマーは、コードがジャンプしている場所とその理由を文書化するようにします。

6
Jared

一般に、gotoの必要性を制限するようにプログラムを設計する必要があります。 OO戻り値の「クリーンアップ」のためのテクニックを使用してください。gotoの使用やコードの複雑化を必要としない方法があります。gotoが非常に役立つ場合があります(たとえば、深くネストされたスコープなど)が、可能であれば回避する必要があります。

5
Marcin

GOTOの欠点はかなりよく議論されています。 1)場合によってはそれらを使用する必要があり、問題を最小限に抑える方法を知っておく必要があります。2)受け入れられるプログラミング手法はGOTO-in-disguiseであるため、注意してください。

1)ASMや.batファイルなどでGOTOを使用する必要がある場合は、コンパイラーのように考えてください。コーディングしたい場合

 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:

つまり、ラベルの目的はdo何かではなく、スキップ何かです。

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-lessコードに変換できます。 GOTO-in-disguiseコードには近づかない方がいいでしょう。これは、偽装されていないgotoと同じスパゲッティコードの問題に巻き込まれる可能性があるためです。

追加:安心させるために、優れたプログラマーのマークの1つは、一般的なルールが適用されないことを認識していると思います。

5
Mike Dunlavey

Gotoの方が優れています 自分自身を繰り返さないでください (DRY)「テールエンドロジック」が一部のケースではなく一般的なケースである場合。特に「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はコードの可読性と保守性さえ改善しますそうでなければ、コピー/貼り付けをして、後で誰かが他の人を更新するかもしれません。したがって、ドグマについて狂信的であると逆効果になる場合もあります。

5
pngaz

Linuxカーネルで使用されているように、クリーンアップに使用されるgotoは、1つの機能で元に戻す必要のある2つ以上の手順を実行する必要がある場合にうまく機能します。手順はメモリ割り当てである必要はありません。これは、コードの一部またはI/Oチップセットのレジスタの構成変更である可能性があります。後藤は少数の場合にのみ必要ですが、多くの場合、正しく使用すると、bestソリューションになる場合があります。彼らは悪ではありません。それらはツールです。

の代わりに...

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;

これら2つの例を考えると、一方が他方よりも望ましいことは明らかです。 RAIIの群衆について...巻き戻しが常に3、2、1の逆順で発生することを保証できる限り、このアプローチには何の問題もありません。最後に、一部の人々はコードで例外を使用しませんコンパイラーに無効にするよう指示します。したがって、すべてのコードが例外セーフである必要はありません。

5
Harvey

Gotoを使用してクリーンアップセクションに移動すると、多くの問題が発生します。

まず、クリーンアップセクションには問題が発生しやすいです。それらは、凝集度が低く(プログラムが何をしようとしているかで説明できる実際の役割はありません)、結合度が高く(正確性はコードの他のセクションに非常に大きく依存します)、まったく例外安全ではありません。クリーンアップにデストラクタを使用できるかどうかを確認します。たとえば、int *pauto_ptr<int> pに変更された場合、pが指すものは自動的に解放されます。

第二に、指摘したように、使用する前に変数を宣言しなければならないため、コードを理解するのが難しくなります。

第3に、gotoのかなり規律ある使用を提案している間、gotoをもっとゆるやかな方法で使用する誘惑があるでしょう。そうすると、コードが理解しにくくなります。

Gotoが適切な状況はほとんどありません。ほとんどの場合、それらを使用したいと思われるとき、それはあなたが間違ったことをしているというシグナルです。

4
David Thornley

C++コードでgotoを使用する理由は2つだけです。

  • レベル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で使用しました。 2箇所のみ。

これは古典的なトピックであるため、ダイクストラの 有害であると考えられる説明 (元はACMで公開されています)で返信します。

4
mstrobl

Cの「すべての関数が単一の出口点を持つ」イディオムの全体的な目的は、すべてのクリーンアップを1つの場所に置くことでした。 C++デストラクタを使用してクリーンアップを処理する場合、それはもはや必要ありません。クリーンアップは、関数の終了ポイントの数に関係なく行われます。したがって、適切に設計されたC++コードでは、この種のことはもう必要ありません。

3
Head Geek

Gotoに夢中になっている人の多くは悪です。ではない。とはいえ、決して必要になることはありません。常により良い方法があります。

この種のことを行うためにgotoを「必要」と思うと、ほとんどの場合、コードが複雑すぎて読みやすく、扱いやすいいくつかのメソッド呼び出しに簡単に分割できることがわかります。呼び出しコードは次のようなことができます:

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

これが完璧というわけではありませんが、問題が何であるかを明確に示すためにすべてのメソッドに名前が付けられるため、従うのがはるかに簡単です。

ただし、コメントを読むと、gotoの処理よりも緊急の問題がチームにあることがわかります。

3
Bill K

提供しているコードは、(ほぼ)C++ファイル内に記述されたCコードです。使用しているメモリクリーニングの種類は、C++コード/ライブラリを使用していないCプログラムでは問題ありません。

C++では、コードは単に安全でなく、信頼できません。 C++では、求めている管理の種類は異なります。コンストラクター/デストラクターを使用します。スマートポインターを使用します。スタックを使用します。 Wordでは、 [〜#〜] raii [〜#〜] を使用します。

あなたのコードは(つまり、C++で、SHOULD)次のように書くことができます:

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には多くのファセットがあります(つまり、スマートポインター、スタック、可変長配列の代わりにベクトルを使用するなど)。しかし、全体としては、可能な限り少ないコードを記述し、適切なタイミングでコンパイラーがクリーンアップできるようにします。

2
paercebal

私はgotoが常に悪いと言うつもりはありませんが、あなたの使用は最も確かです。この種の「クリーンアップセクション」は1990年代初期にはかなり一般的でしたが、それを新しいコードに使用することは純粋な悪です。

1

そのコードには多くの問題があり、そのほとんどはすでに指摘されています。例えば:

  • 関数が長すぎます。いくつかのコードを個別の関数にリファクタリングすることが役立つ場合があります。

  • 通常のインスタンスでポインターを使用すると、おそらく正常に機能します。

  • [〜#〜] stl [〜#〜] auto_ptrなどのタイプを利用しない

  • エラーを誤ってチェックし、例外をキャッチしません。 (OOMのチェックは、OS自体を記述している場合を除き、ほとんどのプラットフォームで無意味であると主張します。

Gotoを必要としたことは一度もありません。gotoを使用することは、大きな問題の兆候であることが常にわかっています。あなたのケースは例外ではないようです。

1
metao

ここで何をしているのかを避ける最も簡単な方法は、このクリーンアップのすべてをある種の単純な構造に入れて、そのインスタンスを作成することです。以下の例:

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
   {
      ...
   }
}
1
Michel

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:;
}
1
Hazok

上記のすべてが有効です。「コードのロット」とマークされたセクションにあるコードの量を減らすことで、コードの複雑さを軽減し、gotoの必要性を軽減できるかどうかを確認することもできます。あなたの例では。 Additionaly delete 0は有効なC++ステートメントです

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ブロックを配置して、ユーザーに非常にまれなメモリ不足 ...について通知する必要があります(OS自体はこれについて通知しません)あまりにも?)

また、常にpointerを使用する必要はないことに注意してください。これらは、動的なことにのみ有用です。 (どこからの入力にも依存しないメソッド内で1つのものを作成することは、実際には動的ではありません)

1
Tamara Wijsman

数年前、私は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;
}
1
ggambett

私は何かを見逃している可能性があります:Pがnullの場合はラベルExitにジャンプし、それがnullでない(そうではない)かどうかをテストして、それを削除する必要があるかどうかを確認します(割り当てられていないため不要です)最初の場所)。

If/gotoはpを削除せず、削除する必要もありません。 gotoをreturn falseに置き換えると同じ効果があります(そしてExitラベルを削除できます)。

Gotoが役立つ場所を知っている唯一の場所は、厄介なパーサー(または字句解析器)の奥深く、そしてステートマシン(大量のCPPマクロに埋め込まれている)を偽装することです。これらの2つのケースでは、非常にねじれたロジックを簡単にするために使用されていますが、それは非常にまれです。

関数(AはA 'を呼び出します)、Try/Catches、およびsetjmp/longjmpsはすべて、難しい構文の問題を回避するための優れた方法です。

ポール。

0
Paul W Homer

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

この方法で試してください:

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

「Lot of code」セクションの「Lot of code」は、おそらくこのセクションを1つ以上のメソッドまたは関数にリファクタリングする必要があることを示しています。

0
Matthew

「GOTO」を使用すると、プログラムの「ロジック」と入力方法、またはそれが機能すると想像する方法が変更されます。

GOTOコマンドを回避することは常に私にとっては有効でした。

ただし、これをAssmeblyレベルで見ると、jump "jump"はGOTOを使用するようなもので、アセンブリで常に使用されていますが、スタックや他のレジスタにあるものをクリアすることができます。渡す。

したがって、GOTOを使用する場合、ココーダーが入力するようにソフトウェアが「表示」されることを確認します。GOTOはソフトウェアの印象に「悪い」影響を与えます。

したがって、これはGOTOを使用しない理由を説明するものであり、代替のソリューションではありません。

0
Filip Ekberg

前のコメントはすべてgotoを使用しない正当な理由です。

あなたのコードを保守する必要があるかもしれない他のプログラマーのために、私は経験から話すことができます。私は重いgotoスパゲッティコードの状況に陥り、私のOOバックグラウンドでデバッグと変更を行うのは悪夢でした。どうしても必要な場合を除き、gotoを使用しないでください。

0
Jeff Schmidt

Alien01書き込み:現在、gotoステートメントが頻繁に使用されるプロジェクトに取り組んでいます。 gotoステートメントの主な目的は、複数のreturnステートメントではなく、ルーチン内に1つのクリーンアップセクションを持つことです。

つまり、mightコードの異なる場所で予約されるリソースを解放するなど、単純な反復的な退屈なルーチンからプログラムロジックを分離する必要があります。

例外処理手法は、プログラムロジックと並行して動作するエラー処理ロジックです。 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;
    }
0
Josef

以前のすべてのコメントから:

  1. 後藤は非常に悪いです
  2. コードが読みにくくなり、理解しにくくなります。
  3. よく知られている問題「スパゲッティコード」を引き起こす可能性があります
  4. 誰もがそれを行うべきではないことに同意します。

しかし、私は次のシナリオで使用しています

  1. これは、1つのラベルのみに進むために使用されます。
  2. gotoセクションは、コードのクリーンアップと戻り値の設定に使用されます。 gotoを使用しない場合は、すべてのデータ型のクラスを作成する必要があります。 int *をクラスにラップする必要があるように。
  3. プロジェクト全体でフォローされています。

私はそれが悪いことに同意しますが、それでもきちんと従えば物事がずっと簡単になります。

0
anand