例外とlongjmp
この辺を読んで、前から言っていたことではあるのですが、例外がどうにも扱いにくいというか精神的負荷が高い理由が説明できる形になった気がします。
例外を単純に(メンバ値とかfinallyとか継承とか抜きで)longjmpで実装しようとすれば、こうなるんですよね。
jump_buf *xxx_error; /* struct xxx_error {}; */ ... jump_buf *old_xxx = xxx_error; /* try */ jump_buf buf; xxx_error = &buf; if(setjump(&buf) == 0){ ... longjump(&jump_buf, 1); /* throw xxx_error(); */ ... }else{ /* catch(xxx_error) */ ... } xxx_error = old_xxx;
例外の種類ごとに、jump_bufのスロットがあるようなイメージ。
要するに、グローバル変数を追ってかないといけないんですよ。
一方、Queenでやろうとしていた形は、素のlongjmpをそのまま使うイメージ。
局所性は保たれますが、例外の種類の増加はイコールでパラメータの増加。指摘を受けたように面倒くさい。
ついでに継承階層も無いので、たとえばファイルを削除するdelete関数があったとします。
void delete(char const *path, jump_buf *failure, jump_buf *not_found);
failureのケースのうち、メディアが読み取り専用のケースをその他の致命的なケースから区別したくなったり、not_foundのケースのうち、pathのシンタックスがそもそも有り得ないケースを実際に見つからないケースから区別したくなったりしたら、引数が2増える上に、deleteの呼び出し側は全て要修正となるわけです。ほとんどはfailureとread_only、not_foundとinvalid_pathに同じ値を渡すことになるでしょう。継承階層が無い弊害です。
逆に、例外の継承階層なんて、互換性を保ったまま種類を増やしたい場合以外には意味が無いような気もするのですが……とりあえずはデフォルトパラメーターread_only = NULLを付けて、read_onlyがNULLだとfailureが使用される、でも良さげです。
この辺から妄想に突入して行きますが、理想を言えば、次のように、デフォルトパラメーター中で他の引数を参照できるといいでしょう。プロトタイプ見ただけで動作わかりますし。
void delete(char const *path, jump_buf *failure, jump_buf *read_only = failure, jump_buf *not_found = failure, jump_buf *invalid_path = not_found);
まあこの辺はこれから作る言語ではどうにでもできます。
それでも同じ関数を20回呼びその全てで全ケース区別したいとすれば、やっぱり面倒なわけです。ついでに、jump_buf引数を完全に省略した場合は、別にその場で落ちてくれてもいいわけです。
int main(jump_buf *crash)
なんてなっていてこのcrashを引き渡していく……なんてのはまっぴらごめんなわけです。
ということでランタイム側にグローバル変数jump_buf crashがあって、deleteのプロトタイプは次が適当でしょう。
void delete(char const *path, jump_buf *failure = &crash, jump_buf *read_only = failure, jump_buf *not_found = failure, jump_buf *invalid_path = not_found); #if WARNING_LEVEL >= 2 #pragma static_assert(delete(failure != crash)); /* deleteを呼ぶ時はfailure引数を渡さないといけないとかなんとか */ #endif
で、繰り返し呼ぶ対策には、束縛というかデフォルトパラメーターの再宣言というかまあそんなのが可能、と。
void delete_files() { jump_buf buf1; if(setjump(&buf1) == 0){ jump_buf buf2; if(setjump(&buf2) == 0){ void my_delete(char const *path) = delete(path, .failure = &buf1, .not_found = &buf2); /* 名前付き引数も使える気分 */ my_delete("1.txt"); my_delete("2.txt"); my_delete("3.txt"); }else{ /* not_found */ } }else{ /* failure */ } }
実際にはこう書きたいです。
void delete_files() { try: { /* このtryもラベルの気分 */ void my_delete(char const *path) = delete(path, .failure = try.failure, .not_found = try.not_found); my_delete("1.txt"); my_delete("2.txt"); my_delete("3.txt"); }catch(not_found){ ... }catch(failure){ ... } }
後ろで宣言した識別子が見えることになるのは気にしない方向で。