switch-caseトラップ
5352 ワード
switch( flag ) {
case 0:
int var = 1;
break;
case 2:
int var2 = 2;
break;
default:
break;
}
上のようなコードは間違っています
error C2360: initialization of 'var' is skipped by 'case' label
error C2361: initialization of 'var' is skipped by 'default' label
かっこをつけると間違いない
switch( flag ) {
case 0:
{
int var = 1;
break;
}
case 2:
{
int var2 = 2;
break;
}
default:
break;
}
————————————————————————————————————————————————————————————————————————————
まず、次のコードを見てみましょう.問題がありますか.
void RunStateMachine()
{
switch(m_status)
{
case TASK_START:
int data = FormDataToSend();
m_mailbox->Send(data);
m_status = TASK_SENT;
break;
case TASK_SENT:
//..
break;
//..
}
}
これが今日書いたコードの一部の原型で、初めて見ても何の問題も感じなかったが、コンパイラは間違っていた:initialization of'data'is skipped by'case'label.
switch-caseの「通り抜け」
ネットで検索して、基準を見て、やっと脈絡が分かった.C++では、switch-caseのcaseは実質的に1つのラベル(label)にすぎず、gotoのラベルのようになっている.caseのコードは局所的な役割ドメインを構成していないが、そのインデントは役割ドメインのような錯覚を与える.つまり、caseで定義されているすべての変数役割ドメインはswitch{...}である.この変数は、後で他のcaseでもアクセスできます.一方、switchは本質的にgotoに相当するため、以下のswitchに続く印刷文は永遠に実行されません.
switch(selector)
{
cout << selector;
case selector_a:
int i = 1;
case selector_b:
//iここでもが表示されます
}
それは間違いと何の関係があるのでしょうか.C++標準規定:
It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps from a point where a local variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has POD type (3.9) and is declared without an initializer. (The transfer from the condition of a switch statement to a case label is considered a jump in this respect.)
つまり、あるプログラムの実行経路がコード内の点A(ある局所変数xがまだ定義されていない)からコード内の別の点B(この局所変数xが定義され、定義時に初期化されている)にジャンプすると、コンパイラはエラーを報告する.このようなジャンプはgoto文の実行、またはswitch-caseの実行によって生じることができる.
まず、PODオブジェクトでは、初期化された宣言がスキップされた場合にのみエラーが報告されます.
switch(selector)
{
case selector_a:
int i = 1;//「int;」なら、コンパイラはエラーを報告せず、次の警告のみです.
case selector_b:
cout << i;//warning:未初期化i を使用
}
変数iが定義されているかどうか、空間サイズはコンパイル期間中に決定され、初期化は実行時の動作であることに注意してください.スキップしたのは、定義ではなく初期化です.
次に、構造関数を明示的に定義したクラスオブジェクトでは、宣言がスキップされた場合にエラーが発生します.
class Employee
{
public:
Employee() {..}
void f() {..}
};
..
switch(selector)
{
case selector_a:
Employee e;//コンパイルエラー!
case selector_b:
e.f();
}
PODオブジェクトに初期化式がある場合、またはクラスに明示的な構造関数がある場合、プログラマはこのオブジェクトを初期化したいことを示します.プログラムの実行によって初期化がスキップされる可能性がある場合、プログラムはプログラマの予想外の不正な状態に入る可能性があります.だから、基準はこのような行為を明確に禁止した.
もちろん、初期化されていないPODオブジェクトについては、後の読み込みに問題があるか、使用前に値を割り当てたかどうかによっては問題がない可能性があります.そして、これらはすべて実行時に知られ、コンパイラは何もできません.だから、多くの場合、間違いを報告しません.
void f(bool set)
{
goto label;
int i;
label:
if (set)
{
i = 3;
}
cout << i;//i初期化はありますか?運転時のみ}
..
f(false);
さあ、問題に戻ります.上記のコンパイルエラーの根本的な原因は、オブジェクトの役割ドメイン「ドラッグ」が長く、複数のcaseを越えていることです.解決策は、各caseのコードをカッコ{}でローカル役割ドメインにカプセル化することである.iは局所的な役割ドメインを有するため,宣言がスキップされるという問題もない.
switch(selector)
{
case selector_a:
{
int i = 1;
}
case selector_b:
//iここでは表示されない}
gotoの「通り抜け」
基準をめくっていると、もう一つ面白い例が見つかりました.この例は,変数を前方に通過する定義に問題があることを説明するためであり,これは上述した.ただし、goto lyを実行すると、オブジェクトaの構造関数が呼び出されます.
void f()
{
// ...
goto lx;//コンパイルエラー、原因は分析を参照
// ...
ly:
A a = 1;
// ...
lx:
goto ly;//OK、lyにジャンプするとaの解析関数が呼び出されることを意味します A b = a;//OK、ここはaの役割ドメインに属しますが、までは実行されません.
}
このプログラムはデッドサイクルを形成し,最初にaのプロファイルが呼び出され,その後構築され,プロファイルされ,絶えず巡回する.では、なぜgoto lyを実行するときにaの構造関数を呼び出すのでしょうか.これは標準にも専門的な説明があります.
Transfer out of a loop, out of a block, or back past an initialized variable with automatic storage duration involves the destruction of variables with automatic storage duration that are in scope at the point transferred from but not at the point transferred to.
プログラム実行経路がループ、ローカル役割ドメイン{...}から離れると、または、初期化されたオブジェクトの前に戻ると、対応する構造関数が呼び出されます.
これは、上記の例では、オブジェクトaの役割ドメインがA a=1からfの右かっこ}に終わることを理解できる.プログラム実行がA a=1にジャンプする前に,明らかにaの役割ドメインには存在しない.役割ドメインを離れるには、途中で異常を投げ出したりreturnを返したりするように、aの構造関数を呼び出す必要があります.
小結
1.switch-caseのswitchはgotoに相当し、caseはgotoラベルに相当する.
2.caseで変数を定義する場合は、周囲に{..}を付ける必要があります.ローカル役割ドメインを形成します.そうしないと、コンパイルエラーが発生します.習慣的にcaseのコードはいつも{...}まとめると、比較的手間が省けます.一部の会社のcoding conventionの一つです.
switch(selector)
{
case selector_a:
{
int i;
}
case selector_b:
//iここでは表示されない}
3.gotoがオブジェクトの初期化を通り抜けると、そのオブジェクトの構造関数が呼び出されます.