えっと,メソッドの宣言時にthrow()はつけます?それともつけません?
これって,誰に対して便利なのかな?
かなりどうでもいい質問ぽくてゴメンナサイ
例外指定 (exception specification) って奴っすね。
根が深いというか、言語仕様がまずいというか、どうでもよくない難しい問題っす。
Q. 誰に対して便利か?
A. その関数(なりクラスなり)を使うユーザにとって便利(かもしれない)
どの例外を投げるか、ないしは、例外を投げないかが明示されるため
Q.つけるかつけないか?
A.プロジェクトの方針しだい
C++ の例外指定って仕様自体がイケテいないので、俺としては以下の2択
・絶対に例外を投げない関数には throw() を明示し(例外処理の中で使う関数など)
それ以外の関数は
・例外を投げるかどうかわからない関数
・特定例外を投げる関数
を区別せずに例外指定をしない
・一切まったく例外指定をしない
のどっちかしかないと思う。
template を使い出すと、関数テンプレートには例外を投げるコードが無くても
typename T が例外を投げるかもしれないし、例外指定を使う意味が無い・・・
tetrapodさんと、ほぼ同意見です。
が、自分は「無駄なことはしないほうが良い」なので、
Exception Specification は「頼まれてもしない」派です(笑)。
理由1 f()thow(){・・・}は例外を投げる可能性がある。
これば、・・・の部分の処理で、例外が発生すれば、結局catchされるので
throw()宣言の「意味」も「効力」も全然無し(笑い)。
理由2 void f() throw(){ throw 1; }は警告(warning C4297)されますが、
コンパイルは通るし例外も発生します(爆)。
んで、意味の無いことはしたくないのが本音ですね(笑い)。
Visual C++ という特定の処理系の振る舞いと
言語規格書が定めている例外仕様の振る舞いとは違う。
> 理由1 f()thow(){・・・}は例外を投げる可能性がある。
言語規格書的には f() throw() は例外を投げることができない。
投げることができない=絶対投げない、ってこと。
例外ハンドラの中で使う関数とかは、そうでなきゃ使い物にならない。
> throw()宣言の「意味」も「効力」も全然無し(笑い)。
「例外指定」が効かないのは Visual C++ が採用している固有の仕様。
多分過去のソースとの互換性維持のために、あえて故意にそうしているだけ。
# C4290 警告の解説参照。将来はサポートするようなことを言っているが多分しない。
言語規格書上は意味も効力もきっちりある。 f() throw() の中から
例外を投げようとすると [例外仕様] に反しているので 15.5.2, 15.4
例外を投げる代わりに unexpected() ハンドラが呼ばれる。
unexpected() は (同一または置換した) 例外を投げるしかできず、不正な場合には
最終的に terminate() ハンドラが呼ばれ、プログラムは強制終了する。
現に GNU C++ では f() throw() の中から例外を投げようとすると、
例外が投げられずにきっちり terminate() する。
例外指定に事実上の意味が無いのは 15.4-10 のため。
15.4-10 処理系は、ある式を実行したとき、その式を含む関数が許さない例外を
送出する、あるいは、送出するかもしれないというだけの理由で、
その式を拒否してはならない。
extern void f() throw(X,Y);
void g() throw(X) { f(); } // OK
f() は 例外 Y を送出するかもしれない
g() は 例外 Y を許さない
が、この呼び出しは適格である。
俺注:もちろん f() が throw Y しようとすると unexpected/terminate
で、やっぱり結論は変わらない。
・まったく書かない
・絶対に例外を投げない関数のみ throw() を明示し他は書かない
のどっちか。
VC限定と書き忘れましたね。失礼しました orz。
>現に GNU C++ では f() throw() の中から例外を投げようとすると、
>例外が投げられずにきっちり terminate() する。
VCも、この部分は守ってほしいもんです(vv;)。
>15.4-10 処理系は、ある式を実行したとき、その式を含む関数が許さない例外を
>送出する、あるいは、送出するかもしれないというだけの理由で、
>その式を拒否してはならない。
これって、警告の対象にしなくても良いという解釈なんでしょうか。
もちろんVCは素通り(vv)/。
一切まったく例外指定をしないでOKっぽいですねぇ
一見親切そうでも,そうでもないなんて余計なお世話になりそうですね
デバイス制御用のDLLを作成して配布を考えるとあったほうが親切かなぁ
なんて考えたんですけど,出ないはずの例外が出るなんてやらしいですしね
まぁ,書くなら何もしないで値型のメンバ変数をただ返すだけのメソッドぐらいですね
でも,結論として私内ルールとして一生書かないと決めてみました
たぶん
ということでありがとうございました
解決とします
実行時に例外をチェックするコードが入るとそれだけオーバヘッドにもなりますしね。
↑例外指定と例外処理は別物っしょ?
> ↑例外指定と例外処理は別物っしょ?
別物ですね。実行時という書き方がよくなかったようですが、
一応実行時の、主に例外処理時の例外仕様の話をしているつもりです。
それがどの程度気になるかと言われれば、昨今のPC上なら誤差かもしれませんが、
例外仕様は静的ではなく、例外仕様があるとその動的なチェックコードが
必要になるため、例外処理時のオーバヘッドは増加しうる理解です。
例えば「throw()で例外が出ないことを前提に最適化可能」とか言っても、
結局現仕様ではunexpectedなどは保証しなければならないので、
現実的にはたいした効果は期待できない代物だと認識してて
自分では基本的に書きませんが、そうでもないものでしょうか。
解決しちゃってるんで,余談かもですけど
int Cxxx::Func1() const
{
return 必ず実態が存在しているint型の変数;
}
とこのような場合は throw()としてもいいと思うんですけど
void Cxxx::Func2() const
{
try
{
}
catch(この世の全ての例外* e)
{
// ここで例外はなかったことにする
}
}
とこんなものを作った場合も throw()としても関係ないんですか?
良いか悪いか、ということなら
例外仕様 func() throw() の文法上解釈=
func() throw() { ... } の中から呼び出し元へ例外が投げられてはならない。
なので、関数の中で例外機構を使いすべての例外をハンドルして外に出さない
のは問題ない(例外仕様の指定方法として問題ないの意味)
ただ、例外=何か異常発生、なわけで
例外を握りつぶして外に出さない=異常が発生していても知る手段が無いということ。
# 時と場合によってはデバッガを使っても知る手段が無かったり・・・
デバッグが非常に困難になったり、
握りつぶしたはずの例外が原因で他のところにも異常が発生したり、
「プログラミングのスタイルとして」は悪いスタイル。
お勧めできない。
内部でなかったことにするタイプでも,throw()と言ってしまうのはなんか,どうなのか
な?って気はしてたんですけど,OKなんですね
でも,悪いスタイルという事で,理解できます
私もそう思いました
>例外を握りつぶして外に出さない=異常が発生していても知る手段が無いということ
という事で,ちなみに例えば
HRESULT Cxxx::Func2() const
{
HRESULT hResult =S_OK;
try
{
}
catch(この世の全ての例外* e)
{
// ここで例外はなかったことにする
hResult =なかった事にした例外の内容;
}
return hResult;
}
とか,あと,GetLastError的なメソッドの用意
なんてするとスタイル論からすると,これは悪ですか?
ほんとに余談過ぎてごめんなさい
そこまでくると
・果たして当該関数の中で例外を使う必然があったのか?
・そもそも「例外」って何?
あたりからの再検討が必要になってくるわけで・・・良い悪いの次元を離れてしまうと思
う。
本質的にありうるエラー、たとえば、ファイルを開くとかディスクを検索するとかで
ファイルが見つからない(権限が無い)ってのはもともと「例外」で実装すべきでない。
ならばそもそも握りつぶすとかエラー値に変換するとかそういう処理はいらない。
最初から「エラーが起こりうる」前提でコードを書くべき。
一方で0で除算したとか、ぬるぽとかは「バグ」である。
バグっているのに通常の処理を継続しても意味が無いことが多い。
assert() は問答無用に終了してしまうわけだが、これを例外におきかえると
・スタックトレースを表示するなどデバッグログを出力して
・データーベースを閉じて
・終了する
など異常系の記述が簡単になり漏れもなくなりいいことずくめ。
それなのに例外を握りつぶす catch は悪なわけだ。
ただ、
・ある DLL は C++ で書かれていて、エラー値を返す代わりに例外を投げる
(設計が悪いのだが、作者が既に退職しているなどして変更できない)
・それを使う EXE は C で書かれていて、例外を扱えない
とかそういう案件であれば、
エラー値を意味する例外をエラー値に変換する必然があるわけで
案件を一番素直に実装できる方策=善だと思われる。
# 真に「例外」時にどうするかは要別考慮。
むやみに catch しないでね。ゴールキーパー以外はハンドで反則ですよ。
http://d.hatena.ne.jp/NAL-6295/20050909/p1
> catch(この世の全ての例外* e)
Windowsの場合、ゼロ除算などWindowsが生成する例外は、
対応するC++の型がないので、catch(...)じゃないと捕らえられません。
あるいは、_set_se_translatorを使って型にマップするか。
ついでにMFCの話。
MFCでウィンドウプロシージャ内で例外が生じるとき、
・MFCの例外はウィンドウプロシージャが捕捉しcallerには送出されない、
・Windowsが生成する例外はcallerに送られる、
となっています(vs2003)。
C++の例外はcallerに送られますが、callerには理解できない例外です
(というか、callerに送出してはいけないと思います)。
あと、
OnPaint内でMFCの例外が生じた場合は、何のエラー表示もなく処理は継続されます。
> Windowsの場合、ゼロ除算などWindowsが生成する例外は、
> 対応するC++の型がないので、catch(...)じゃないと捕らえられません。
それは、C++としてはゼロ除算を例外で取られられるという仕様はありませんから、
本来は「そうなることを条件等で事前に避ける」べきであって、
VCの実装でWindowsの構造化例外(SEH)を使っているために「取れてしまう」だけでは。
# C++では基本的にSEHは使うな(C++の例外を使え)とMSさんは仰られていますね。