本当にtry-catchで処理すべき問題??

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
spaaaark・∀・
記事: 66
登録日時: 10年前
住所: 埼玉
連絡を取る:

本当にtry-catchで処理すべき問題??

#1

投稿記事 by spaaaark・∀・ » 10年前

コード:

 //データを受理する呼び出し元のコード
Read hoge;
hoge.Process(/*データ*/);

//Read::Process関数の中身
void Read::Process(/*データ*/){
  try{
    /*処理*/   // ここでたくさんの読み込み関数を実行しており、読み込み関数内でEOFに到達すると例外クラスErrEOFをthrowする
  } catch(ErrEOF &) { /*読み込み完了のLog書き出し*/ }
}
というCSVファイル読み込みの方法を扱っているのですが、エラー処理となる例外機構でEOF検出ってしてもよいものなのでしょうか?
確かにこのコードはいちいちEOFが検出されたかどうかのコードを随所に書く必要がなくなるのですが、
友人曰くお勧めしないと言われたところにより気になった次第です。よろしくお願いします。
クリエイティブな生活で刺激的な毎日を!

beatle
記事: 1281
登録日時: 12年前
住所: 埼玉
連絡を取る:

Re: 本当にtry-catchで処理すべき問題??

#2

投稿記事 by beatle » 10年前

C++の規格をちょっと調べたのですが、該当する記述が見つかりませんでしたので想像で書きます。

try開始から例外発生時までに行ったローカル変数への変更は、例外が発生してcatchに移動したときに保持されている保証は無いと思います。
setjmp/longjmpを用いて例外っぽい処理を書こうと思うと、まさにローカル変数への変更は担保されませんので、try-catchがsetjmp/longjmpを用いて実装されている処理系だと同様に担保されない可能性はあります。

コード:

jmp_buf jbuf; // グローバル変数

void f()
{
    int i = 0;
    if (setjmp(jbuf) == 0) {
        i = 10;
        longjmp(jbuf, 1);
    } else {
        // ここで i == 10 である保証はない。
    }
}
つまり、EOF検出に例外を利用するなら、この辺りの事情を考慮すべきかもしれないということです。
(もしかしたらC++の規格のどこかに、こういう気持ち悪いことが起きないと記述されているかもしれないので断言は出来ません)

YuO
記事: 947
登録日時: 13年前
住所: 東京都世田谷区

Re: 本当にtry-catchで処理すべき問題??

#3

投稿記事 by YuO » 10年前

spaaaark・∀・ さんが書きました:エラー処理となる例外機構でEOF検出ってしてもよいものなのでしょうか?
確かにこのコードはいちいちEOFが検出されたかどうかのコードを随所に書く必要がなくなるのですが、
友人曰くお勧めしないと言われたところにより気になった次第です。よろしくお願いします。
一般的ではありませんし,「例外」機構を使うべき所ではないと思います。
EOFは通常必ず起きる状況であって,特別な状況ではありません。
少なくとも,初回のEOFは例外無しで取得できるようにした方がよいでしょう。
# 例外原理主義的な,JavaですらEOFが例外で検出される構造にはなっていません。

beatle さんが書きました:C++の規格をちょっと調べたのですが、該当する記述が見つかりませんでしたので想像で書きます。
try開始から例外発生時までに行ったローカル変数への変更は、例外が発生してcatchに移動したときに保持されている保証は無いと思います。
例外が発生すると,最寄りの型が一致するcatchハンドラに「制御が移り」ます (ISO/IEC 14882:2011 15.1 ¶2)。
tryブロック開始から例外発生までのローカル変数のデストラクタは呼び出されますが (同 15.2 ¶3)。
この時,try ブロックの外側にあるローカル変数は,まだブロックの末尾に達していませんから,当然生存しています (同 3.7.3)。
オブジェクトが死んでいない以上,値が勝手に変更されることもありません。
beatle さんが書きました:setjmp/longjmpを用いて例外っぽい処理を書こうと思うと、まさにローカル変数への変更は担保されませんので、try-catchがsetjmp/longjmpを用いて実装されている処理系だと同様に担保されない可能性はあります。
setjmp/longjmpを実装として使うのは勝手ですが,勝手にオブジェクトを破棄してはいけません。

beatle
記事: 1281
登録日時: 12年前
住所: 埼玉
連絡を取る:

Re: 本当にtry-catchで処理すべき問題??

#4

投稿記事 by beatle » 10年前

YuO さんが書きました:
beatle さんが書きました:C++の規格をちょっと調べたのですが、該当する記述が見つかりませんでしたので想像で書きます。
try開始から例外発生時までに行ったローカル変数への変更は、例外が発生してcatchに移動したときに保持されている保証は無いと思います。
例外が発生すると,最寄りの型が一致するcatchハンドラに「制御が移り」ます (ISO/IEC 14882:2011 15.1 ¶2)。
tryブロック開始から例外発生までのローカル変数のデストラクタは呼び出されますが (同 15.2 ¶3)。
この時,try ブロックの外側にあるローカル変数は,まだブロックの末尾に達していませんから,当然生存しています (同 3.7.3)。
オブジェクトが死んでいない以上,値が勝手に変更されることもありません。
なるほど。オブジェクトが死んでいなければ勝手に変更されない、という規格になっているのですね。
ということは、私が示したコード例において、longjmpの後の変数iは「死んでいる」状態ということですか。(i == 10の状態からlongjmpによりi == 0に変更されているので)
YuO さんが書きました:
beatle さんが書きました:setjmp/longjmpを用いて例外っぽい処理を書こうと思うと、まさにローカル変数への変更は担保されませんので、try-catchがsetjmp/longjmpを用いて実装されている処理系だと同様に担保されない可能性はあります。
setjmp/longjmpを実装として使うのは勝手ですが,勝手にオブジェクトを破棄してはいけません。
私は「破棄」するとは言っていません。変数が破棄されるのではなく、変数への変更が無効化される、ということです。

setjmp/longjmpを用いて例外処理を行うコンパイラの場合、私がコード例に書いたような現象が起きるかも、と言っているのです。
sjljを有効にしてコンパイルしたGCCを持っていないので実験できていませんけれど。

アバター
tk-xleader
記事: 158
登録日時: 13年前
連絡を取る:

Re: 本当にtry-catchで処理すべき問題??

#5

投稿記事 by tk-xleader » 10年前

 Ideone( http://ideone.com/eJW6Za )で実験したところ、setjmp/longjmpを以下のように使用すれば、確かに2回目のprintfの時点でiの値は0になりました。

コード:

#include <stdio.h>
#include <setjmp.h>
 
jmp_buf jbuf;
 
int main(void) {
	int i = 0;
	if(setjmp(jbuf)==0){
		i = 10;
		printf("(1) %d\n",i);
		longjmp(jbuf,1);
	}else{
		printf("(2) %d\n",i);
	}
	return 0;
}
 これはCの規格において次のように書かれていることから、規格に合致した挙動です。
すべてのアクセス可能なオブジェクトは、longjmpが呼び出された時の値を保持し、かつ抽象計算機のその他のすべての構成要素は、longjmpが呼び出された時の状態を保持する。ただし、対応するsetjmpマクロの呼出しを含む関数中の、volatile修飾型でない動的記憶域期間をもつオブジェクトの値が、setjmp呼出しとlongjmp呼出しの間に変更された場合に不定となることを除く。(JIS X3010「プログラム言語C」 7.13.2.1 longjmp関数)
 従って、変数iはvolatile修飾されてないため、このような状況では値が不定となってしまいます。

 しかし、C++の規格においてtryブロックから例外送出までのオブジェクトの変更について、不定とするという文言はないため、catchブロックまでの間に値が不定になることはまずないと考えてよいでしょう。実際、SJLJ有効のGCCにおいて次のコードを実行しても、iの値は2回とも10でした。

コード:

#include <iostream>

using namespace std;

void throwing(){
    throw 1;
}

int main()
{
    int i = 0;
    try{
        i = 10;
        cout << "(1)" << i << endl;
        throwing();
    }catch(int x){
        cout << "(2)" << i << endl;
    }
    return 0;
}

YuO
記事: 947
登録日時: 13年前
住所: 東京都世田谷区

Re: 本当にtry-catchで処理すべき問題??

#6

投稿記事 by YuO » 10年前

beatle さんが書きました:
YuO さんが書きました:
beatle さんが書きました:setjmp/longjmpを用いて例外っぽい処理を書こうと思うと、まさにローカル変数への変更は担保されませんので、try-catchがsetjmp/longjmpを用いて実装されている処理系だと同様に担保されない可能性はあります。
setjmp/longjmpを実装として使うのは勝手ですが,勝手にオブジェクトを破棄してはいけません。
私は「破棄」するとは言っていません。変数が破棄されるのではなく、変数への変更が無効化される、ということです。
変更によってオブジェクトが生成されていた場合,破棄しないといけません。
オブジェクトの変更は,オブジェクトの生成や破棄を伴う可能性が常に付随します。

現実問題として,例外で勝手にオブジェクトが変更されるようでは,例外が使い物になりません。
newしたオブジェクトがリークするわけですから。
# その先にはファイルなどの共有リソースが紐付いているかもしれない。
beatle さんが書きました:setjmp/longjmpを用いて例外処理を行うコンパイラの場合、私がコード例に書いたような現象が起きるかも、と言っているのです。
そういうことが起きないように例外を実装するのが実装側の責務です。
ローカル変数の値を含めた環境が巻き戻るのはsetjmp/longjmpの仕様であって,例外の仕様ではありません。

beatleさんが書かれていることは杞憂であって,大元の質問に関連して言えばこのことを考慮する必要は全くないです。

beatle
記事: 1281
登録日時: 12年前
住所: 埼玉
連絡を取る:

Re: 本当にtry-catchで処理すべき問題??

#7

投稿記事 by beatle » 10年前

tk-xleader さんが書きました:しかし、C++の規格においてtryブロックから例外送出までのオブジェクトの変更について、不定とするという文言はないため、catchブロックまでの間に値が不定になることはまずないと考えてよいでしょう。実際、SJLJ有効のGCCにおいて次のコードを実行しても、iの値は2回とも10でした。
実験までしていただいてありがとうございます。
不定とする文言は確かに見つかりませんでした。longjmpの場合は不定になる旨が明示してあり、例外では不定云々が記述されていないことを考えれば、不定にならないということで良さそうですね。
YuO さんが書きました:beatleさんが書かれていることは杞憂であって,大元の質問に関連して言えばこのことを考慮する必要は全くないです。
杞憂であってほしいなあと思っていましたので、それが判明して嬉しいです。

本題から逸れる書き込みで場を汚してしまい、すみませんでした。


さて、例外ではsetjmp/longjmpのような嫌らしい問題が起こらないことが分かりましたので、安心して本題を考えられます。

YuOさんが仰るように、JavaでさえEOF検出は例外を使いません。だから自分でもEOF検出に例外を使わないようにしよう、と考えるのもアリです。

しかし私は、EOF検出に例外を使っても良いと思います。
良くあるファイル読み込みAPIは、読み込みを1箇所で行うことを暗に想定し、戻り値でEOFを教える設計になっていると感じます。
しかし、今回は読み込み箇所が複数に散らばっているため、EOFが来たら例外を飛ばし、1箇所でEOF判定をする設計は一理あると思います。
プログラミングは型にはまらないことも重要です。

ただ、例外を使わずとも、gotoを使うなどしてEOF処理を1箇所で行う方法もあろうかと思います。spaaaark・∀・さんが、例外は真の例外のみに使うべきだ、という考えの持ち主ならそうするのが良いと思います。

アバター
spaaaark・∀・
記事: 66
登録日時: 10年前
住所: 埼玉
連絡を取る:

Re: 本当にtry-catchで処理すべき問題??

#8

投稿記事 by spaaaark・∀・ » 10年前

beatleさん、YuOさん、tk-xleaderさん、議論の方をありがとうございます。
ひとまず、手に持っているソースコードは例外を排出しないように変更しました。もちろんコードの可読性は40%くらい下がりました。

分析クラス内で一回一回EOFかどうか判断すると、まったく同じコードを何回も書かないといけない事態に陥ってしまいますよね。
その部分が気に入らない点があるのですが…、そこは関数化することで目をつむろうかなとも思っています。
とりあえず、今まであったEOF検出用の例外判定クラスを不正なEOFを検出する例外クラスに切り替えて作業を進めています。

なんだか返信を見ていますと、こういう問題は個人差であるような気もしてきます。
一般から考えるとタブーのようにも見えますが、判定が難しい場合ではあえて使うこともできそうです。
その観点は、例外処理をどのように使うか、という観念も少しだけ関わってきそうです。

一般的には使わないようにする、という自分なりの回答が出来上がりましたので、このトピックは解決とさせていただきます。
みなさんありがとうございました。
クリエイティブな生活で刺激的な毎日を!

閉鎖

“C言語何でも質問掲示板” へ戻る