5

序章

この質問は、次の質問から派生しています。 名前付きループ イディオム : 危険ですか? . 元の質問を読みたくない人のために、それはそのようなことをすることについてでした:

named(label1) for(int i = 0 ; i < 10 ; i++) {
    for(int j = 0 ; j < 10 ; j++) {
      if(some_condition)
            break(label1); // exit outer loop
      }
}

この新しい質問は、「名前付きループ」イディオムの改良版に関するものです。この投稿全体を読むのが面倒な場合は、この投稿の「例」セクションに直接アクセスして、私が話していることを明確に理解してください。

設計上の欠陥

残念ながら、質問は純粋な技術的な質問というよりも賛否両論の議論であったため、すぐに閉じられました (そして後で再開されました)。SO Q&A形式に合わなかったようです。さらに、提示したコードにはいくつかの欠陥がありました。

  • キーワードbreakがマクロによって再定義されました
  • マクロは小文字を使用して記述されています
  • それはいくつかの恐ろしいものをコンパイル可能にしました(少なくともMSVCを使用して):

    int foo() {
    
       named(label1) for(int i = 0 ; i < 10; i++)
       {
          if(some_condition)
          {
              break(label1);  // here it's ok, the behavior is obvious
          }   
       }
    
       break(label1); // it compiles fine without warning... but the behavior is pretty obscur!
    
    }
    
  • 見栄えの良いコードが壊れる可能性があります。たとえば、次のコードは、スコープの問題によりコンパイルされません。

    int foo() {
    
    named(label1)  for(int i = 0 ; i < 10 ; i++)
           named(label2)  for(int j = 0 ; j < 10 ; j++)
            if(i*j<15)
                cout << i*j << endl;
            else
                break(label2);
     }
    

より安全な実装

名前付きループの安全なバージョンを入手するために、これらの問題をすべて修正しようとしました。より一般的には、ループだけでなく、任意のスコープを途中で終了するために使用できるため、ブレーク可能なスコープとも呼ばれます。

2 つのマクロNAMEDとの定義は次のBREAKとおりです。

    #define NAMED(bn)   if(const bool _YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_ = true)\
                            goto _named_loop_identifier__##bn##_;\
                        else\
                            _break_the_loop_labelled_##bn##_:\
                            if(true)\
                                {}\
                            else\
                                if(! _YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_)\
                                    goto _break_the_loop_labelled_##bn##_;\
                                else\
                                    _named_loop_identifier__##bn##_:\


    #define BREAK(bn) if(!_YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_){} \
                        else goto _break_the_loop_labelled_##bn##_

「未使用の変数」、「参照されていないラベル」、「明示的な中括弧の提案」など、MSVCまたはGCCによって生成される可能性のある警告も回避されるため、見苦しく面倒です。さらに、誤って使用するとコンパイルされるべきではありません。その場合、エラーメッセージは理解できます。例えば ​​:

    NAMED(loop1) for(int i = 0 ; i < 10; i++) {
        NAMED(loop2) for(int j = 0 ; j < i ; j++) {
            cout << i << "," << j << endl;
            if(j == 5) {
                BREAK(loop1);   // this one is okay, we exit the outer loop
            }
        }
        BREAK(loop2); // we're outside loop2, this is an error
    }

前のコードはコンパイルされず、2 番目のコンパイラ エラー メッセージBREAK: '_YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_loop2_` は非常に明示的です。

質問をする前に、これらの構造の (相対的な) 有用性を説明するために 2 つの例を示します。

break外側のループ:

     NAMED(myloop) for(int i = 0 ; i < rows; i++) {
         for(int j = 0 ; j < cols ; j++) {
            if(some_condition) {
                 BREAK(myloop);
            }
         }
     }

特定のスコープを終了します:

NAMED(myscope1) {
    cout<< "a";
    NAMED(myscope2)
    {
        cout << "b";
        NAMED(myscope3)
        {
            cout << "c";
            BREAK(myscope2);
            cout << "d";
        }
        cout << "e";
    }
    cout <<"f";
}

このコードは : を出力しますabcf

私の質問

私の質問が何であるかを定義する前に、私のトピックが 10 分で閉じられるのを見たくないので、それが何ではないかを定義する必要があります。

「それでいいです」でも「どう思いますか」でもありません。または「それは役に立ちますか?」でも、stackoverflow は議論の場ではないようです。とにかく、私はすでに答えを知っています:「マクロは悪です」、「後藤は悪です」、「代わりにラムダを使用してください」。

代わりに、私の質問は、技術的な正確性とプログラミング エラーに対する堅牢性についてです。このコンストラクトをできるだけ安全なものにしたいと考えています。

  • ユーザーがそれを誤用してもコンパイルできる可能性はありますか? 元の実装の明らかな問題を修正しようとしましたが、C++ は非常に複雑で、何か見落としているのではないでしょうか?

  • 見栄えの良いコードを静かに壊すことができますか? これが私の主な関心事です。他の C++ 機能 (例外処理、デストラクタ呼び出し、その他の条件文、またはその他...) に干渉できますか?

私の目的は、この構造が本質的に危険ではないことを示すことです。他のプログラマーはそれを明確に理解していない可能性があるため、実際のコードで使用するのは非常に悪い考えであることはすでに知っていますが、個人的なプロジェクトで使用するのに十分安全ですか?

EDIT:ブール変数は現在const(Jens Gustedtに感謝)です。if後でs をsに置き換えて、forそのように使用したときに偽の警告を削除できることを確認します。

if(true)
     BREAK(label);

EDIT2: JensGustedt が気づいたように、ifステートメント内の変数宣言は C では許可されていません (C++ のみ)。それをループに置き換えるもう1つの理由。

4

2 に答える 2

2

これをテストしたと思いますが、宣言したbool変数NAMED(bn)は、それが宣言されている if ステートメントが存在する同じブロックで、後でまだ使用可能ではありませんか? (: そうしないと、あなたのイディオムは機能しません。:)

私はこれが安全であることを見ることができます:

{
    NAMED(one) { ... }
}
BREAK(one);

BREAK(nb);コンパイラは行で大騒ぎします

しかし、これはまだ安全ではないようです:

{
    NAMED(bn) { ... }
    BREAK(bn);
}

コンパイラが変数を定義済みとして受け入れ、大騒ぎしないという意味で安全ではありません。しかし、それは無限ループを形成する可能性があり、その結果、黙ってプログラムが中断されます。

-ジェシー

PS:ブロックを終了する方法に関係なく、ブロックが実行されるように定義されているためtry..finally、干渉しません。ブロックを避けようとしない限り、問題ありません。finallytryfinally

P(PS)S: 私が見た他の構造との唯一の本当に奇妙な相互作用はこれです:

#if DEBUG
    NAMED(bn)
#endif
    while(true)
    {
        BREAK(BN);
    }

そしてそれは病的です!;-D DEBUG では正常にコンパイルされますが、RELEASE ではコンパイル エラーが発生します。

于 2012-06-17T15:36:22.063 に答える
1

私は本当にC ++の側面について答えることができませんが、あなたはそれをCとラベル付けしたので...

通常、そのようなことにif/構文を使用するのはあまり良い考えではありません。なぜなら、奇妙なマッチングやそのようなものelseに対して適切な場所で警告を見つける良い意味の C コンパイラが常に見つかるからです。else

私は通常、forそのような構成をP99に使用します。ぶら下がりelse(またはそれに関する誤った警告) を回避します。またfor、必要な方法でローカル変数を配置できる C の唯一の場所でもあります。これらは許可されていないifwhile、C++ の場合と同様です。

「安全」な面では、変数を宣言するregister bool constだけでなく、bool. そのため、誰もそれを変更しようとしたり、そのアドレスを取得して変更したりすることさえできませんでした。

しかし、あなたがそれを使用している特定の目的のために、私はあまり好きではありませforgoto。あなたが実際に行っているのは、スタックの巻き戻しです。C では、これを適切に行うための構造体が 1 つあります。つまり、setjmp/longjmpです。C++ では、おそらくtry/になりcatchます。

于 2012-06-17T15:31:24.497 に答える