はじめに
突然ですが、以下のコードのどこに誤りがあるでしょうか?
#include <iostream>
using namespace std;
int main() {
bool f = somefunc();
switch (f) {
case true:
int res = somefunc1();
cout << res << endl;
break;
case false:
float res = somefunc2();
cout << res << endl;
break;
}
}
答え
プログラムを実行すると以下のようなエラーが出ます。
switch.cpp: In function 'int main()':
switch.cpp:10:10: error: jump to case label
10 | case false:
| ^~~~~
switch.cpp:8:13: note: crosses initialization of 'int res'
8 | int res = somefunc1();
| ^~~
switch.cpp:11:15: error: conflicting declaration 'float res'
11 | float res = somefunc2();
| ^~~
switch.cpp:8:13: note: previous declaration as 'int res'
8 | int res = somefunc1();
| ^~~
簡単に言うと、変数res
が重複して宣言されています、ということですね。
なぜこんなことになるのか
C++のswitch文の仕様として、breakしない限りマッチしたcaseの後続のcaseブロックまでも実行される、というものがあります。これは、「フォールスルー(fall through)」と呼ばれるものです。
以下のプログラムをご覧ください。
#include <iostream>
using namespace std;
int num = 0;
int main() {
switch (num) {
case 0:
cout << "0" << endl;
case 1:
cout << "1" << endl;
case 2:
cout << "2" << endl;
default:
cout << "default" << endl;
}
}
このプログラムを実行すると、
$ ./badswitch
0
1
2
default
case 0以下の全処理が実行されてしまいました。意図して書く場合は便利なものではありますが、バグの温床になることの方が多い気がします。
さて、本題に戻ると、フォールスルーという仕様があって複数のcaseブロックが実行される可能性があるために、caseの中で重複して変数を宣言することができないわけです。res、tmpなどの一時的にしか使わない変数名に対してres_intなどとするのは馬鹿馬鹿しいですね。
解決策
プログラム
#include <iostream>
using namespace std;
int main() {
bool f = somefunc();
switch (f) {
case true: {
int res = somefunc1();
cout << res << endl;
break;
} case false: {
float res = somefunc2();
cout << res << endl;
break;
}
}
}
各caseブロックを、{}で挟んでしまえば解決です。
余談
"{"や"}"が縦に2つ並ぶのは気持ち悪いのでcaseブロックをインデントしましたが、こうすると全体で2インデントされることになって無駄に深くなってしまうのが難点ですね。
ブロック文のお話
実は、C++では、{}で挟むことで、どこにでも新たなスコープを作り出すことができます。これは「ブロック文」と呼ばれます。
int main() {
int x;
{
int tmp = somefunc1();
x = somefunc2(tmp);
}
float y;
{
float tmp = somefunc3();
y = somefunc4(tmp);
}
}
こんなことだってできます。
これにより、
- 変数の使いまわしができるようになる
- 変数の有効範囲が制限されて可読性が上がる
等、複数のメリットがあります。
先ほどのswitch文では、これを適用したに過ぎないわけです。
まとめ
ブロック文は便利!!