throw()について

[戻る]
hirocco 2011/02/27(日) 18:18:41
えっと,メソッドの宣言時にthrow()はつけます?それともつけません?
これって,誰に対して便利なのかな?

かなりどうでもいい質問ぽくてゴメンナサイ

tetrapod 2011/02/28(月) 12:32:20
例外指定 (exception specification) って奴っすね。
根が深いというか、言語仕様がまずいというか、どうでもよくない難しい問題っす。

Q. 誰に対して便利か?
A. その関数(なりクラスなり)を使うユーザにとって便利(かもしれない)
どの例外を投げるか、ないしは、例外を投げないかが明示されるため

Q.つけるかつけないか?
A.プロジェクトの方針しだい

C++ の例外指定って仕様自体がイケテいないので、俺としては以下の2択
・絶対に例外を投げない関数には throw() を明示し(例外処理の中で使う関数など)
 それ以外の関数は
 ・例外を投げるかどうかわからない関数
 ・特定例外を投げる関数
 を区別せずに例外指定をしない

・一切まったく例外指定をしない

のどっちかしかないと思う。

template を使い出すと、関数テンプレートには例外を投げるコードが無くても
typename T が例外を投げるかもしれないし、例外指定を使う意味が無い・・・

仲澤@失業者 2011/02/28(月) 13:31:38
tetrapodさんと、ほぼ同意見です。
が、自分は「無駄なことはしないほうが良い」なので、
Exception Specification は「頼まれてもしない」派です(笑)。

理由1 f()thow(){・・・}は例外を投げる可能性がある。
   これば、・・・の部分の処理で、例外が発生すれば、結局catchされるので
   throw()宣言の「意味」も「効力」も全然無し(笑い)。

理由2 void f() throw(){   throw 1;  }は警告(warning C4297)されますが、
      コンパイルは通るし例外も発生します(爆)。

んで、意味の無いことはしたくないのが本音ですね(笑い)。

tetrapod 2011/02/28(月) 14:44:31
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() を明示し他は書かない
のどっちか。

仲澤@失業者 2011/02/28(月) 15:00:55
VC限定と書き忘れましたね。失礼しました orz。

>現に GNU C++ では f() throw() の中から例外を投げようとすると、
>例外が投げられずにきっちり terminate() する。

VCも、この部分は守ってほしいもんです(vv;)。

>15.4-10 処理系は、ある式を実行したとき、その式を含む関数が許さない例外を
>送出する、あるいは、送出するかもしれないというだけの理由で、
>その式を拒否してはならない。

これって、警告の対象にしなくても良いという解釈なんでしょうか。
もちろんVCは素通り(vv)/。

hirocco 2011/02/28(月) 16:00:04
[[解決]]
一切まったく例外指定をしないでOKっぽいですねぇ

一見親切そうでも,そうでもないなんて余計なお世話になりそうですね
デバイス制御用のDLLを作成して配布を考えるとあったほうが親切かなぁ
なんて考えたんですけど,出ないはずの例外が出るなんてやらしいですしね

まぁ,書くなら何もしないで値型のメンバ変数をただ返すだけのメソッドぐらいですね
でも,結論として私内ルールとして一生書かないと決めてみました
たぶん

ということでありがとうございました
解決とします

Ban 2011/03/01(火) 01:36:44
実行時に例外をチェックするコードが入るとそれだけオーバヘッドにもなりますしね。

επιστημη [E-Mail] [HomePage] 2011/03/02(水) 19:58:49
↑例外指定と例外処理は別物っしょ?

Ban 2011/03/03(木) 09:17:31
> ↑例外指定と例外処理は別物っしょ?

別物ですね。実行時という書き方がよくなかったようですが、
一応実行時の、主に例外処理時の例外仕様の話をしているつもりです。

それがどの程度気になるかと言われれば、昨今のPC上なら誤差かもしれませんが、
例外仕様は静的ではなく、例外仕様があるとその動的なチェックコードが
必要になるため、例外処理時のオーバヘッドは増加しうる理解です。

例えば「throw()で例外が出ないことを前提に最適化可能」とか言っても、
結局現仕様ではunexpectedなどは保証しなければならないので、
現実的にはたいした効果は期待できない代物だと認識してて
自分では基本的に書きませんが、そうでもないものでしょうか。

hirocco 2011/03/04(金) 10:31:53
解決しちゃってるんで,余談かもですけど

int Cxxx::Func1() const
{
  return 必ず実態が存在しているint型の変数;
}
とこのような場合は throw()としてもいいと思うんですけど

void Cxxx::Func2() const
{
  try
  {
  }
  catch(この世の全ての例外* e)
  {
    // ここで例外はなかったことにする
  }
}

とこんなものを作った場合も throw()としても関係ないんですか?

tetrapod 2011/03/04(金) 11:10:52
良いか悪いか、ということなら

例外仕様 func() throw() の文法上解釈=
func() throw() { ... } の中から呼び出し元へ例外が投げられてはならない。
なので、関数の中で例外機構を使いすべての例外をハンドルして外に出さない
のは問題ない(例外仕様の指定方法として問題ないの意味)

ただ、例外=何か異常発生、なわけで
例外を握りつぶして外に出さない=異常が発生していても知る手段が無いということ。
# 時と場合によってはデバッガを使っても知る手段が無かったり・・・
デバッグが非常に困難になったり、
握りつぶしたはずの例外が原因で他のところにも異常が発生したり、
「プログラミングのスタイルとして」は悪いスタイル。
お勧めできない。

hirocco 2011/03/04(金) 11:37:04
内部でなかったことにするタイプでも,throw()と言ってしまうのはなんか,どうなのか
な?って気はしてたんですけど,OKなんですね
でも,悪いスタイルという事で,理解できます
私もそう思いました

>例外を握りつぶして外に出さない=異常が発生していても知る手段が無いということ
という事で,ちなみに例えば
HRESULT Cxxx::Func2() const
{
  HRESULT hResult  =S_OK;

  try
  {
  }
  catch(この世の全ての例外* e)
  {
    // ここで例外はなかったことにする
    hResult  =なかった事にした例外の内容;
  }
  return hResult;
}
とか,あと,GetLastError的なメソッドの用意

なんてするとスタイル論からすると,これは悪ですか?

ほんとに余談過ぎてごめんなさい

tetrapod 2011/03/04(金) 13:28:31
そこまでくると
・果たして当該関数の中で例外を使う必然があったのか?
・そもそも「例外」って何?
あたりからの再検討が必要になってくるわけで・・・良い悪いの次元を離れてしまうと思
う。

本質的にありうるエラー、たとえば、ファイルを開くとかディスクを検索するとかで
ファイルが見つからない(権限が無い)ってのはもともと「例外」で実装すべきでない。
ならばそもそも握りつぶすとかエラー値に変換するとかそういう処理はいらない。
最初から「エラーが起こりうる」前提でコードを書くべき。

一方で0で除算したとか、ぬるぽとかは「バグ」である。
バグっているのに通常の処理を継続しても意味が無いことが多い。
assert() は問答無用に終了してしまうわけだが、これを例外におきかえると
・スタックトレースを表示するなどデバッグログを出力して
・データーベースを閉じて
・終了する
など異常系の記述が簡単になり漏れもなくなりいいことずくめ。
それなのに例外を握りつぶす catch は悪なわけだ。

ただ、
・ある DLL は C++ で書かれていて、エラー値を返す代わりに例外を投げる
(設計が悪いのだが、作者が既に退職しているなどして変更できない)
・それを使う EXE は C で書かれていて、例外を扱えない
とかそういう案件であれば、
エラー値を意味する例外をエラー値に変換する必然があるわけで
案件を一番素直に実装できる方策=善だと思われる。
# 真に「例外」時にどうするかは要別考慮。

むやみに catch しないでね。ゴールキーパー以外はハンドで反則ですよ。
http://d.hatena.ne.jp/NAL-6295/20050909/p1

ロマ 2011/03/06(日) 15:35:21
> catch(この世の全ての例外* e)

Windowsの場合、ゼロ除算などWindowsが生成する例外は、
対応するC++の型がないので、catch(...)じゃないと捕らえられません。
あるいは、_set_se_translatorを使って型にマップするか。

ついでにMFCの話。
MFCでウィンドウプロシージャ内で例外が生じるとき、
・MFCの例外はウィンドウプロシージャが捕捉しcallerには送出されない、
・Windowsが生成する例外はcallerに送られる、
となっています(vs2003)。
C++の例外はcallerに送られますが、callerには理解できない例外です
(というか、callerに送出してはいけないと思います)。

あと、
OnPaint内でMFCの例外が生じた場合は、何のエラー表示もなく処理は継続されます。

Ban 2011/03/06(日) 20:23:50
> Windowsの場合、ゼロ除算などWindowsが生成する例外は、
> 対応するC++の型がないので、catch(...)じゃないと捕らえられません。

それは、C++としてはゼロ除算を例外で取られられるという仕様はありませんから、
本来は「そうなることを条件等で事前に避ける」べきであって、
VCの実装でWindowsの構造化例外(SEH)を使っているために「取れてしまう」だけでは。

# C++では基本的にSEHは使うな(C++の例外を使え)とMSさんは仰られていますね。

ロマ 2011/03/07(月) 20:22:10
> それは、C++としてはゼロ除算を例外で取られられるという仕様はありませんから、
> 本来は「そうなることを条件等で事前に避ける」べきであって、
> VCの実装でWindowsの構造化例外(SEH)を使っているために「取れてしまう」だけで
は。

例としてゼロ除算を挙げたのは、まずかったですね。
スタックオーバーフローだといかがでしょう。


あと、SEHがcatch(...)で取れるのは、少なくともVC7.1の仕様のようです。

vs2003付属のmsdnのC++ Language Referenceの
Exception Handling\C++ Exception Handling\The try, catch, and throw Statements
には

If the exception-declaration statement is an ellipsis (...), 
the catch clause handles any type of exception, 
including C exceptions and system- or application-generated exceptions such as
memory protection, divide by zero, and floating-point violations. 

との記述があります。VC固有の機能という制限は見当たりませんでした。

hirocco 2011/03/07(月) 20:56:28
ん〜,答えはでているんですけど
> catch(この世の全ての例外* e)

って言ったのは, 絶対起こりえないような環境に人為的にした場合
throw()をつけてもいいかどうかという質問への例えで,
本当は例外が起きているのに throw()をつけてしまうとですね
tetrapodさんが仰ったように,例外に気づくことができなくなるけど,
そんな状態でもthrow()をつけてしまってもいい物かどうかということです

いわゆる値型のメンバ変数(言葉あってるかな?)を返すだけで,
例外は起きないと思いますっていうときだけ,throw()がついていれば
わかりいいとは思いますけどね

それでもまだnewもされていない,ポインタの値も不定値のままの状態なのに
メソッドを呼び出すとか値型でも気まずいですよね?

となるとstaticの値型の静的メンバ変数とか定数を返すとかのメソッドにしか
throw()はつけられなくなってきますよね

で,throw()はそこまで厳密なのかどうかって言うことが知りたかったんです,はい

0除算などはそのクラス独自なものじゃなくて,言語の根本ぶぶんかなって,
そこはBanさんが仰った「そうなることを条件等で事前に避ける」で正解かと思います
スタックオーバーフローなどは恐らくクラスの設計に問題があるか,
そのクラスの使用者側の問題になるかなと。。。
そして,その辺りはデバグの時点でクリアになっていないと商品としてはマズイのでねぇ

たとえばそのクラスはDLLやActiveXで作られていて,operator[]とかで内部クラスを返す
メソッドを持っていると仮定してみます
で,そのDLLやActiveXが内部で持っている要素以上のインデックスをそのユーザが指定し
てしまっても,必ずNULLを返しておくようにして,例外を起こさないとthrow()をうたって
おくと,そのユーザはどうかと思うけどインデックスをメクラで総当りしても,大丈夫で
すよって,いう事になってちょっと親切なのかなと気になってました
まぁ,結構つけなくてもいいんじゃないかという意見が多かったので,つけない方向で行
きまぁす

長々と偉そうにゴメンナサイ。。。

ITO 2011/03/07(月) 22:59:55
うーーん、
僕は、グループでソフトを作るっていうことはしないので、仲澤さんと同意見ですね。
 ただ、グループで作成となると、今までtetrapodさんの意見を読んでみると
いい機能だと思います。
自分が作成したクラスが他の人によって使われるとしたら、
  その中でディスク・ネットワーク・パイプ等の入出力でそのクラスの禁止事項を
 C++の例外としてユーザ設定する。
というのは意味があると思います。
ただ、仲澤さんとtetrapodさんが述べているマイクロソフト固有の「無視する」という
のは問題ですね。
 VC2005でC/C++基準に近くなり、VC2010ではほぼ同じと思っているのですが
駄目ですか?
 となると、そういったクラス同士のインターフェースの保護(でOKですか?)は、
protected等のアクセス指定子で指定するのが基本になるのですかね?

ITO 2011/03/07(月) 23:12:06
> って言ったのは, 絶対起こりえないような環境に人為的にした場合
> throw()をつけてもいいかどうかという質問への例えで,
工場で稼動する制御ソフトを作ったことがあります。
そういったソフトは、
  「いかなる状況でも止まってはならない。」
 
 又は
  「いかなる状況でも非常時は必ず停止する。 
    また、どんな時もファンクションキー等が
   押されたときに動作を再開する。」
でしたね。
  まー、出来ないものは出来ないですね。

 一番難しかったのが、
   稼働中に人為的にRS-232Cケーブルが抜差しされても
   何もなかったように動作する。
 ですね。
 一定間隔でシーケンサーと通信してたので難しかったです。

   
   
   

Ban 2011/03/08(火) 09:25:06
言語仕様(ISO/IEC14882)には規定がない以上、MSDN

hirocco 2011/03/08(火) 09:59:53
> 稼働中に人為的にRS-232Cケーブルが抜差しされても
> 何もなかったように動作する。
人為的に...
断線や通信の衝突,バッファオーバーラン等のエラーではなく
明らかに通信中真っ只中を狙っての抜き差しや,高速抜き差しとかも対応しなきゃって
事?
これは無理でしょ?
何かが起きた場合,「必ず人間に通知し,安全に終了させる」ならともかく,何もなかった
ように動作するって,ひどい要求ですね

>「いかなる状況でも止まってはならない。」
これはありがちですね
でも,馬鹿みたいな要求ですよね
そういう場合は
シーケンサやローガーを背負わせて,PCは負担をなるべく感じない状態でのデータ収集の
みに徹しますねぇ
インターフェイス的にはまるでPCから制御してます風を装って。。。

> 一定間隔でシーケンサーと通信
これは通信コストが馬鹿にならない時はIOやA/Dによるハンドシェイクにしちゃいますね

でもそこまでカスタムすると,ラダーも書かなきゃいけなくなるので,ベラボーな金額に
設定するか,断っちゃいますwww

で,それを制御させるDLLの依頼で別の人がメイン部分を書くとなると throw() とかどう
なんでしょうねって事に,
DLLもクラスにしなきゃ関係ないんですけどねぇ
"throw()=絶対に例外はありません"だとすると,ヘタに書かないほうがよい感じですね
この辺で何かあったら,モメそうな予感もしますし。。。

Ban 2011/03/08(火) 10:00:18
言語仕様(ISO/IEC14882)には規定がない以上、MSDNの規定はVCの仕様かと。

any exceptionがキャッチできるとはありますが
スタックオーバフローに関するexceptionを投げる規定はないです。
overflow_errorの流用なりその他なりを投げる事を仕様は要求しないですし
参照できなければ言語的には未定義動作です。

で、特定の処理系(VC)が特定の環境(Win)固有の機能(SEH)を実装に使っていて
その副次効果で未定義動作がどう振る舞うかは自由ですし
文書化する事も可能ですが、可搬性は期待できないかと。

Ban 2011/03/08(火) 10:04:13
書きかけおくってました…orz
例外仕様の本旨から外れてますね、すみません。 

ITO 2011/03/08(火) 12:11:52
> これは無理でしょ?
> 何かが起きた場合,「必ず人間に通知し,安全に終了させる」ならともかく,
> 何もなかった
> ように動作するって,ひどい要求ですね

うーん、
生産ラインのソフトとしては普通みたいです。

> 何かが起きた場合,「必ず人間に通知し,安全に終了させる」ならともかく,
現場で作業する人がほとんど外国人なのでPCを操作できないのが本音みたいです。

> シーケンサやローガーを背負わせて,PCは負担をなるべく感じない状態での
> データ収集のみに徹しますねぇ
> インターフェイス的にはまるでPCから制御してます風を装って。。。
シーケンサーは、制御・タッチパネル等を行い、PCはデータ通信・データ処理を行う。
のようです。

> これは通信コストが馬鹿にならない時はIOやA/Dによるハンドシェイクにしちゃい
> ますね
溶接等のスパークのノイズが問題で、RS-232C RS-422等を使うのが基本みたいです。


> でもそこまでカスタムすると,ラダーも書かなきゃいけなくなるので,
> ベラボーな金額に設定するか,断っちゃいますwww
PC側は動作すべきであって、フローの提出はないです。
ただ、相手がシーケンサーなので打ち合わせ用の手書きのフローは必要ですね。
まぁー、通信フローしかなくシーケンサー主導なのでほとんど見せるときないです。

ITO 2011/03/08(火) 12:18:19
> 言語仕様(ISO/IEC14882)には規定がない以上、MSDNの規定はVCの仕様かと。
なるほど、統一してほしいですが難しそうですすね。
ユーザー定義に関する規定があればいいんですよね。

ryo 2011/03/08(火) 12:44:36
私も生産ラインを扱ったことありますが

「現場のおっさんは、
マウス操作すら嫌がる、パソコンなんてわからない人たちなんだから、
できるだけソフトでなんとかしてくれ」

とよく言われました

maru 2011/03/08(火) 13:32:59
> > 稼働中に人為的にRS-232Cケーブルが抜差しされても
> > 何もなかったように動作する。
> 人為的に...
> 断線や通信の衝突,バッファオーバーラン等のエラーではなく
> 明らかに通信中真っ只中を狙っての抜き差しや,高速抜き差しとかも対応しなきゃって
> 事?
> これは無理でしょ?
> 何かが起きた場合,「必ず人間に通知し,安全に終了させる」ならともかく,何もなかっ> ように動作するって,ひどい要求ですね
多分、抜き(障害発生)差し(復旧)なので、「障害がなくなったら自動復旧してくれ
よ」、という意図だと思います。
別にひどい要求ではなく、普通の要求ですね(生産現場では)。

一番大変なのが、
>   「いかなる状況でも止まってはならない。」
これがプログラム変更に関しても適用されること。まあシーケンサ側の話ですが。

現場の人たちは制御プログラムをその制御対象が動作している最中に変更しろっ
て言うんです。それも制御を止めず、運転途中の制御データと引継ぎながら。
私にはインタプリタ方式しか思い浮かばないんですけど。

> 「現場のおっさんは、
> マウス操作すら嫌がる、パソコンなんてわからない人たちなんだから、
> できるだけソフトでなんとかしてくれ」
現場では軍手などを着用しているんでPCの操作なんかできない、ってのもよく聞きま
す。

ロマ 2011/03/08(火) 20:15:43
Banさん、ありがとうございます。了解です。

hiroccoさん、横槍失礼しました。

Ban 2011/03/09(水) 01:58:52
> なるほど、統一してほしいですが難しそうですすね。

そうですね。例えばスタックオーバフローの検出とか例外処理を
すべての処理系に求めてしまうと、例えばCPUやOSの支援のないプアな
組み込み環境などではオーバヘッドのせいで「使えない言語」に
成り果てるでしょうし。

> ユーザー定義に関する規定があればいいんですよね。

例外の投入自体は自分で投げる限りユーザの好きな型が投げられますし、
unexpectedやterminatedのハンドラも一応定義できますが、
処理系の内部で投げてくれ、というのはユーザ定義にはならないと思います。
言語仕様にはコンパイルオプションとかいう概念ありませんし、
もし常に例外をチェックするオーバヘッドがかかるなら、それはC++の
ゼロオーバヘッドルール(使わない機能でコストを発生させない)にも反しますし。

# ちなみにVC2005やg++で試した所やはりスタックオーバフローはcatch(...)で取れず。

ロマ 2011/03/09(水) 06:43:23
スタックオーバーフローとぬるぽをVS2003で試したところ、
DebugとReleaseで動作が異なり、
Releaseだとcatch(...)に引っ懸かりませんでした。

あと、
スタックオーバーフローの場合は、
単にEXCEPTION_STACK_OVERFLOWが来ると思ったのですが、
コンパイラはcalleeの先頭でスタックサイズを調べるコードをつけていました。

# 私は、このスレッドではDebugモードだけ試して書き込んでいました。
# 例外仕様throw()からの例外送出もReleaseモードだと動作が違うようです。

古いスレッドには追加発言できません。