63

現在、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つのラベルに進む場合は、問題ありません(?)。

4

35 に答える 35

60

コードのクリーンアップとはどういう意味かわかりませんが、C ++には「リソースの取得は初期化です」という概念があり、デストラクタがクリーンアップする必要があります。

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

詳細については、次のページをご覧ください: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 とコードに関して、基本的に 2 つのポイントがあります。

  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場合、例外がスローされます。少なくともこのパラメータのセットでは、が返されることはありません。type のインスタンスを取り、例外をスローする代わりに実際に戻る特別なplacement-newオーバーロードがあります。ただし、このバージョンは通常のコードではほとんど使用されません。一部の低レベル コードまたは組み込みデバイス アプリケーションは、例外の処理にコストがかかりすぎるコンテキストでそれを利用できます。operator new0std::nothrow0

deleteハラルドが言ったように、あなたのブロックにも同様のことが当てはまります: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

一般的に、そして表面的には、ラベルが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 = 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

他の回答(および彼らのコメント)がすべての重要なポイントをカバーしていると思いますが、まだ適切に行われていないことが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と宣言され、最後に割り当てられたのはいつですか?この上のある時点で。関数全体を読んで、何が返されるかを確実に理解する必要があります。

そして、どうすれば記憶が解放されることを自分自身に納得させることができますか?

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

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

于 2008-12-18T22:03:52.697 に答える
7

私がプログラミングをしてきた 8 年間で、私は goto をたくさん使ってきました。そのほとんどは、GW-BASICのバージョンと1980 年の本を使用していた最初の年でした。特定の場合に使用されます。私が 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を使用したり、コードを複雑にしたりする必要がない方法があります。gotosが非常に役立つ場合があります(たとえば、深くネストされたスコープ)が、可能であれば避ける必要があります。

于 2008-12-18T20:43:06.637 に答える
5

Linux カーネルで使用されているように、クリーンアップに使用される goto は、元に戻す必要がある可能性のある 2 つ以上のステップを 1 つの関数で実行する必要がある場合にうまく機能します。ステップはメモリ割り当てである必要はありません。これは、コードの一部または 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;

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

于 2008-12-22T19:00:17.490 に答える
5

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

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:

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

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 コードには近づかないほうがいいと思います。

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

于 2008-12-22T19:46:56.897 に答える
4

これは古典的なトピックであるため、有害と見なされるダイクストラのGo-toステ​​ートメント(元々はACMで公開されていました)で返信します。

于 2008-12-18T21:13:06.467 に答える
4

Goto は、「最後尾のロジック」がすべてのケースではなく一部のケースに共通する場合に、自分自身を繰り返さない(DRY) ことを提供します。特に「switch」ステートメント内では、一部の switch-branch に末尾共通性がある場合に 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

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

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

第 2 に、ご指摘のとおり、使用するずっと前に変数を宣言する必要があり、コードの理解が難しくなります。

第三に、あなたは goto のかなり規則正しい使用法を提案している一方で、それらをより緩い方法で使用したくなる誘惑にかられ、そうするとコードが理解しにくくなります。

goto が適切な状況はほとんどありません。ほとんどの場合、それらを使用したくなるのは、間違ったことをしているという合図です。

于 2008-12-18T20:48:44.923 に答える
3

Cのevery-function-has-a-single-exit-pointイディオムの全体的な目的は、すべてのクリーンアップを1か所にまとめることでした。C ++デストラクタを使用してクリーンアップを処理する場合、それは不要になります。関数にある出口点の数に関係なく、クリーンアップが実行されます。したがって、適切に設計されたC ++コードでは、この種のものはもはや必要ありません。

于 2008-12-18T21:01:31.153 に答える
3

多くの人が goto でびっくりしますが、それは悪です。ではない。とはいえ、必要になることはありません。常により良い方法があります。

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

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

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

ただし、コメントを読むと、あなたのチームには goto 処理よりも差し迫った問題があることがわかるはずです。

于 2008-12-19T01:29:50.957 に答える
2

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

C ++では、コードは単に安全でなく、信頼性がありません。C ++では、要求している管理の種類は異なります。コンストラクタ/デストラクタを使用します。スマートポインタを使用します。スタックを使用します。つまり、 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には多くの側面があります(つまり、スマートポインター、スタック、可変長配列の代わりにベクトルを使用するなど)が、全体としては、コードをできるだけ少なくして、コンパイラーが適切なタイミングでクリーンアップできるようにすることです。

于 2008-12-20T17:11:48.430 に答える
1

終了コードにgotoを使用するのは悪いことだと思います。これは、終了関数を使用したり、必要に応じて終了関数の値を返すなど、オーバーヘッドの少ないソリューションが他にもたくさんあるためです。ただし、通常、メンバー関数では、これは必要ありません。そうでない場合は、コードの膨張が少し多すぎることを示している可能性があります。

通常、プログラミング時に「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 に答える
1

C ++でGOTOラベルを使用することはプログラミングの悪い方法です。オブジェクト指向プログラミング(デコンストラクター!)を実行し、プロシージャをできるだけ小さくすることで、必要性を減らすことができます。

あなたの例は少し奇妙に見えます、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;
}

メモリ不足の問題を処理するメインプログラムにtrycatchブロックを配置する必要があります。これは、非常にまれですが、メモリ不足についてユーザーに通知します...(OS自体もこれを通知しませんか?)

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

于 2008-12-18T21:06:24.747 に答える
1

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

于 2008-12-18T20:55:12.087 に答える
1

それが常に悪いとは言いませんgotoが、あなたの使い方は間違いなく悪いことです。この種の「クリーンアップ セクション」は 1990 年代の初めにはかなり一般的でしたが、新しいコードにそれを使用することはまったくの悪です。

于 2008-12-18T21:41:09.233 に答える
1

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

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

  • 通常のインスタンスがおそらく問題なく動作する場合にポインターを使用します。

  • auto_ptr などのSTL型を利用しない

  • エラーを誤ってチェックし、例外をキャッチしません。(OS 自体を作成していない限り、OOM のチェックは大部分のプラットフォームでは無意味であると主張します。メモリが不足すると、ソフトウェアが修正できるよりも大きな問題が発生するためです)

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

于 2008-12-19T01:55:49.343 に答える
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

数年前、私は 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 に答える
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;
}

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

于 2008-12-18T20:59:18.757 に答える
0

「GOTO」を使用すると、プログラムの「ロジック」と、どのように解釈するか、またはどのように機能するかを想像する方法が変わります。

GOTOコマンドを回避することは常に私のために働いてきたので、あなたがそれを必要とするかもしれないと思うとき、あなたがおそらく必要とするのは再設計だけだと思います。

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

したがって、GOTOを使用するときは、ココーダーが解釈するようにソフトウェアが「表示」されることを確認します。GOTOは、ソフトウェアに「悪い」影響を及ぼします。

つまり、これはGOTOを使用しない理由の説明であり、代替のソリューションではありません。これは、他のすべてがどのように構築されるかによって非常に重要になるためです。

于 2008-12-18T21:02:30.467 に答える
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

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

あなたのコードを維持する必要があるかもしれない他のプログラマーにとって、ロジックに従うのは非常に難しいという経験から言えます。私は重いgotoスパゲッティコードの状況に陥り、オブジェクト指向のバックグラウンドを持っていたため、デバッグして変更を加えるのは悪夢でした. はい、このコードはクリーンアップ関数にも goto を使用しています。必要がないときは非常にイライラします。どうしても必要な場合を除き、goto を使用しないでください。

于 2008-12-18T21:17:27.970 に答える
0

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

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

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

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

ポール。

于 2008-12-18T23:19:31.017 に答える
0

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

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

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

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

私はそれが悪いことに同意しますが、適切に従えば物事ははるかに簡単になります.

于 2008-12-18T21:33:07.953 に答える
0

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

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

例外処理テクニックは、プログラム ロジックと並行して動作するエラー処理ロジックです。ステートメントとまったく同じように、他のコードブロックに制御を移動する機能を提供しながら、このような分離を提供するため、より洗練されたソリューションです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

後藤のこのことはどこから来たのかわかりません...

コンパイルされた言語では、すべての条件付き命令(if、、、、など)は「cmp」「jmp」または「j??」で解決されます。マシンコード(jmp IS goto)。switchforwhile

実際、非常によく最適化されたコードは最適な実行パスを認識しているため、「goto」を使用する必要があります...スタックが使用されていない場合は、メソッドや呼び出しよりも、gotosを使用して線形コードを実行する方がよいでしょう(「call」 = "push" + "jmp")。

C ++でGOTOを使用すべきでない理由はまったくありません。生成されたコードは、とにかくどこでも「jmp」でいっぱいです。

goto命令が解釈されるときに、gotoの宛先が必ずしも解釈されていないため、これはスクリプト(多くの場合利用できない場合)でのみ問題になります。

gotoに対する最初の議論は、コードをチェックするのが難しいということです。

それはばかげています:コードはチェックされるように作られていません(または一度だけ):実行されるように作られ、小さくて高速です。最適化されたコードは、冗長性を回避し(同じ命令を再利用することにより)、実行時チェックを回避する必要があります(ハードウェアの誤動作に関連しない例外は、実行時ではなく設計時にすべて回避する必要があります)。

于 2012-09-11T16:01:55.100 に答える