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