一般的な以下の書き方のマクロをswitch文中で使った時に
#define MACRO(arg1, arg2) do { \
/* ... */ \
} while(0)
switch(hoge)
{
case 0:
MACRO(1,2);
別の処理;
break;
・・・
}
マクロ内でcase 0:自体をbreakしたいと思ってbreak文を書いても、
do{}while(0)をbreakしてしまうため、「別の処理」を実行してし
まいます。
とりあえず今はそういう場合だけdo{}while(0)を使わない以下のよ
うなマクロを書いて解決しているのですが、
#define MACRO(arg1, arg2){ \
/* ... */ \
}
もうちょっとスマートな解決方法はないでしょうか?
#define MACRO(arg1, arg2) if (0); else { \
/* ... */ \
}
なんてのは?
すみません、これは副作用的には大丈夫なのでしょうか?
なぜdo{}while(0)を使わないのを気にしているかというと、
http://www.kouno.jp/home/c_faq/c10.html#4
マクロ本体は、単にカッコ{}でくくった複数の文であってはならない。
なぜならマクロが(見た目は一つの文として、かつ余計なセミコロンを
付けて呼ばれたときに)、else節を持つif/else文のifが成立したとき
の分岐に使われたときに文法エラーとなる。
あたりを気にしているからなのですが、どうなのでしょうか…自分では
上記の文章も完全には理解できないもので…
> マクロ本体は、単にカッコ{}でくくった複数の文であってはならない。
たとえば
#define MACRO() { ほげほげ }
と定義すると、 if ( ... ) MACRO(); else { ... } てのが
if ( ... ) { ほげほげ } ; else { ... }
と展開され、else 直前の ; がエラーを起こします。
なので末尾に;を打ってもいいように do { ほげほげ } while(0)
と定義するわけ。
if (0); else の場合同様のエラーが起こります。
気になるなら使わないでください。
あー、ごめん。
「末尾の ; 問題」を気にしないんだったら
#define MACRO(arg1, arg2){ \
/* ... */ \
}
これで十分ですわね。
てか、if (0); else { ... } じゃ { ... } と等価です。
for-scopeのおまじないと混同してました。申し訳なし。_o/L
私的には、複数行に展開されるようなマクロは基本的に使わない人なので
末尾の;の為に do {} while(0)と言う書き方をするって言うのすら
今初めて見ました。
επιστημηさんの説明を読んでなるほどそういうことかと思いましたけど、
個人的にはマクロ使いまくりで展開後の状態が一目でわからないような
使い方は混乱を招きそうな気がして使いません。
C++じゃ、マクロの仕様は推奨されていなかったと思いますし。
最近じゃ、定数定義もconst使うので条件コンパイルぐらいにしかマクロは
使わなくなりましたねぇ。
あとは、C言語用のインターフェイスが必要でエラーコードの定義が必要な場合くらいかなぁ。
複数行に展開されるマクロっつーことで…
頻繁に追加・削除が発生する文字列群を用意して、それぞれの文字列長をコンパイルタイム
に求めておきたい場合に、こんなの使いました。
#define STRINGS(ENTRY) \
ENTRY(a), \
ENTRY(bb), \
ENTRY(ccc), \
ENTRY(dddd)
#define TO_STRING(x) #x
#define TO_LENGTH(x) (sizeof(TO_STRING(x)) - 1)
const char* str_array[] = {
STRINGS(TO_STRING)
};
const int length_array[] = {
STRINGS(TO_LENGTH)
};
STRINGSにENTRYを追加・削除するだけでOK。
さて、C++的にはどう書くのがスマートなんでしょうか?
…で、
#define MACRO(arg1,arg2) if (1) { なんやかんや } else do {} while(0)
ではどうでしょか。
> #define MACRO(arg1,arg2) if (1) { なんやかんや } else do {} while(0)
なるほど、これならswitchのbreakも効きそうですね。
でも、MACROの中で勝手にbreakされて後の処理が動かないと言うのは
個人的にはいやですね。
展開された後が見えないとMACROの後が実行されない状況がひと目で理解できないし。
私ならMACROを使わないで済む方向で考えたいです。
後からの可読性が落ちそう。
よく考えたら、余計なお世話でした。
申し訳ない。
新しい知識が仕入れられたので感謝します。
失礼しました。
> 展開された後が見えないとMACROの後が実行されない状況がひと目で理解できないし。
わしもそう思う。
ナカミを知らずに使うのがマクロなのに、ナカミを知らんと使えないわけやから。
とはいえ、途中でbreakするなんざマクロにしかできないオイシサであるのは事実ナリ。
すみません、連休前で仕事に忙殺されてまして返答が遅れました。
> #define MACRO(arg1,arg2) if (1) { なんやかんや } else do {} while(0)
なるほど!これでいけますね。有難うございます。
>ナカミを知らずに使うのがマクロなのに、ナカミを知らんと使えないわけやから。
今まであまり深く考えていなかったのですが、ご指摘の通りあまりよくない書き方だと気づきました。なるべく使わない方向で修正してみます。
>途中でbreakするなんざマクロにしかできないオイシサであるのは事実ナリ。
私の場合大量のcaseがあるswitch文で、最初に共通の例外処理をして、当てはまったらbreakするという時に使ってます(ただし例外処理が無いcaseもある)。こういう場合で例外処理に関数内の変数を大量に使っている場合に重宝してます(マクロの代わりに関数にすると、変数を渡すのが面倒なので)。
どちらにせよ、あまり良くない書き方ですね。
#define MACRO()\
if( a>1 && b>2 && c>3 && … ){\
break;\
}
foo{
int a,b,c,…;
switch(hoge)
{
case 0:
MACRO();
処理0;
break;
case 1:
//無し
処理1;
break;
case 2:
MACRO();
処理2;
break;
case 3:
・・・
}
}
C++なら、例外を使用。Cなら、マクロで例外もどきを使用、とか。
MACROマクロは条件式のみに留めておくといいかも。
#define TRY do
#define THROW goto TRY_TAG_ERROR
#define CATCH while(0); goto TRY_TAG_RESUME; TRY_TAG_ERROR:
#define FINNALY TRY_TAG_RESUME:
#define MACRO (a != 10)
int main(void)
{
int a = 1;
TRY{
switch( a ){
case 1:
if( MACRO ){
THROW;
}
puts("1");
break;
default:
puts("default");
}
}
CATCH{
puts("error!");
}
FINNALY{
getchar();
}
return 0;
}
もうちょい真面目に書くなら、
#define TRY do
#define THROW(name) goto TRY_TAG_ERROR_##name
#define CATCH(name, last) while(0); goto TRY_TAG_RESUME_##last; TRY_TAG_ERROR_##name:
#define FINNALY(last) TRY_TAG_RESUME_##last:
#define MACRO (a != 10)
int main(void)
{
int a = 1;
TRY{
switch( a ){
case 1:
if( MACRO ){
THROW(A);
}
puts("1");
break;
case 2:
if( MACRO ){
THROW(B);
}
puts("2");
break;
default:
puts("default");
}
}
CATCH(A, F){
puts("error! A");
}
CATCH(B, F){
puts("error! B");
}
FINNALY(F){
getchar();
}
return 0;
}
なるほど、こういう書き方もありますか。参考になります。
(ちなみにCで開発しています)
今回は仕事で書いているソースなため時間的に修正は難しい
ですが、次の機会があれば試してみたいと思います。
既にお気づきの方も多いとは思いますが、バグがありましたので、修正します。
名前なしの方も同様に、do, while(0); を削除してください。
#define TRY
#define THROW(name) goto TRY_TAG_ERROR_##name
#define CATCH(name, last) goto TRY_TAG_RESUME_##last; TRY_TAG_ERROR_##name:
#define FINNALY(last) TRY_TAG_RESUME_##last:
一応、下記に簡単にこの考えをまとめてみました。
C言語で例外処理もどき
http://www.atmark.gr.jp/~s2000/r/rtl/try.html
詳しい解説有難うございます。
(と言っても私だけの為に作ってくれたわけでもないでしょうが^^;)
次の機会に使ってみようと思います。
> C言語で例外処理もどき
> http://www.atmark.gr.jp/~s2000/r/rtl/try.html
冒頭「C++言語の例外処理」ですが、
デストラクタ使って RAII パターンの C++ だと finally がないので、
例としては Java あたりの方がよい気がします。
# Windows 限定の構造化例外(SEH)なら Cでも使えますね。
ツイート | ![]() |