例外の扱い方(必須処理の書き方について)

解決


あむ  2005-12-03 22:35:04  No: 19023

あむと言います。

例外処理における、try...except, try...finally の使い方についてですが、Delphi では、

  (1) try...except :  例外は except 節で処理され、その後は例外は解除される。

  (2) try...finally :  例外の有無にかかわらず finally が実行され、例外は解除されない。

と理解しています。

そこで質問が二つ。。

■ 一つ目
http://www.borland.co.jp/tips/cbuilder/cb001/delbcb_7.html
のような、よくあるサンプルでは、

    try
      try
        .....
      except
        .....(例外処理)
      end;
    finally
      .....(必須の後処理)
    end;

のように書かれていますが、この except と finally のネストは逆なのでは?
(例外発生時は、内側の except で例外が解除されるため、外側の try...finally は try ブロックの意味をなしていないのでは?  また、例外が発生しなかった場合も、try ブロックの意味をなしていない。という理由。)

■ 二つ目
上記 (1) の理由から、except 処理した後はそのまま処理が継続されるので、わざわざ try を2重にしなくても、

    try
      .....
    except
      .....(例外処理)
    end;
    .....(必須の後処理)← finally に書かれるであろう処理

これだけで十分なのではないか?
たとえば、

    mst := TMemoryStream.Create;
    try
      ........
    except
      ........
    end;
    mst.Free;

という具合です。

言葉にするなら、

「事後処理は、finally に書くのと、try...except ブロックの外に(普通に)書くことは等価である」

ということなのかなと思うのですが、どう思われますか?

ずいぶんと長い間探しているのですが、こういうコードは見かけないため質問させていただきました。

※ もちろん、内側が try...except on ... で、例外を特定している場合は別です。

以上、稚拙な質問で恐縮ですが、ご教授頂けると幸いです。
よろしくお願いいたします。


にしの  2005-12-03 23:32:34  No: 19024

> ■ 二つ目
> 上記 (1) の理由から、except 処理した後はそのまま処理が継続されるので、わざわざ try を2重にしなくても、

>     try
>       .....
>     except
>       .....(例外処理)
>     end;
>     .....(必須の後処理)← finally に書かれるであろう処理

> これだけで十分なのではないか?

except句で例外が発生した場合はどうするんでしょうか。
もちろん、tryの多重処理にかかわらず、例外が発生しない箇所(かつ、ブロックの後処理が不要な箇所)でのtryは冗長ですので、省略すべきです。


あむ  2005-12-04 05:50:51  No: 19025

> except句で例外が発生した場合はどうするんでしょうか。

except 内での例外発生に対しては、except 内で try 処理するのが正しいのでは?

そのときの為に、外側に finally を置くのはお門違いではないですか?

たとえば、

var
  res: Integer;
  i: Integer;
begin
  i := 0;
  try
    res := 1 div i;
  except
    try
      res := 1 div i;
    except
      res := 10;
    end;
  end;
  showMessage( '--> ' + p_pp + ' ' + IntToStr(res) );
end;

といった感じ。
(except で例外が発生するようなコードを書くことは少ないですが)

本題に戻って、具体的な質問に変えますが、以下の二つのコード、世間一般では大抵、前者が提示されると思います。
が、後者で良いのではないのか(多少でも前者より不利になる要素はあるのか)? というのが今回の趣旨です。

引き続き、よろしくお願いいたします。

■ 世間一般によくあるコード例

procedure TMain.BMP2JPGStream(const bmp: TBitmap; var mst: TMemoryStream);
var
  jpg: TJpegImage;
begin
  jpg := TJpegImage.Create;
  try
    try
      jpg.Assign(bmp);
      jpg.SaveToStream(mst);
    except
      on e: Exception do ShowMessage( '[Err] ' + e.Message );
    end;
  finally
    FreeAndNil(jpg);
  end;
end;

■ でも、これで良いのでは? のコード例

procedure TMain.BMP2JPGStream(const bmp: TBitmap; var mst: TMemoryStream);
var
  jpg: TJpegImage;
begin
  jpg := TJpegImage.Create;
  try
    jpg.Assign(bmp);
    jpg.SaveToStream(mst);
  except
    on e: Exception do ShowMessage( '[Err] ' + e.Message );
  end;
  FreeAndNil(jpg);  // ←必ず実行される
end;


ふつうは  2005-12-04 06:29:12  No: 19026

例外が発生したら処理を中断してロールバックします。

> 上記 (1) の理由から、except 処理した後はそのまま処理が継続

継続されないので抜ける前にfinallyで処理するという事だと思います。


@1  2005-12-04 07:03:06  No: 19027

http://onigiri.s3.xrea.com:8080/delphi/index.php?%CE%E3%B3%B0%2F%A5%A4%A5%F3%A5%C8%A5%ED%A5%C0%A5%AF%A5%B7%A5%E7%A5%F3
ここを熟読するといいかも!


あむ  2005-12-04 08:47:35  No: 19028

「ふつうは」さん、どうも。

> > 上記 (1) の理由から、except 処理した後はそのまま処理が継続
>
> 継続されないので抜ける前にfinallyで処理するという事だと思います。

表現が悪かったですか。。
「そのまま処理が継続」されるのは、try ブロックの外の話です。
例外の有無にかかわらず、外側の finally に書かかれるコードには処理は流れます。
そのため、そのコードを finally に書く意味があるのか?  という質問です。


あむ  2005-12-04 08:58:26  No: 19029

「@1」さん、どうも。

良い資料の情報をありがとうございます。
大変興味深く読ませていただきました。

が、おっしゃりたいことはもしかして、私がサンプルで提示した上記のソースが、その文献で言われるところの「例外を食っている」になっている。というだけの話ですかね?


ふつうは  2005-12-04 09:48:21  No: 19030

exitする場合を考えてみましょう。
finallyが無い場合、こんな感じになります。

  except
    on e: Exception do
    begin
      ShowMessage( '[Err] ' + e.Message );
      FreeAndNil(jpg);
      exit;
    end;
  end;
  FreeAndNil(jpg);

FreeAndNil(jpg);を2回書かずに済むという点でfinallyを使った方が見通しが良くなります。


あむ  2005-12-04 18:21:10  No: 19031

> exitする場合を考えてみましょう。
> finallyが無い場合、こんな感じになります。
>    :
> FreeAndNil(jpg);を2回書かずに済むという点でfinallyを使った方が見通しが良くなります。

すみません、「exit する場合」と、finally を使うと「FreeAndNil(jpg);を2回書かずに済む」というのは、具体的にどういうことをおっしゃってるんでしょうか?

finally を使うと、

    except
      on e: Exception do
      begin
        ShowMessage( '[Err] ' + e.Message );
        exit;
      end;
    end;
  finally
    FreeAndNil(jpg);
  end;

と書ける(FreeAndNil(jpg);が一回で済む)という意味ですかね?
であればこの場合、except 内で exit してますから、当然 finally の処理は実行されずに exit してしまいます。(finally の意味がない。というか、そもそもおかしい。)

それとも、

    except
      on e: Exception do
      begin
        ShowMessage( '[Err] ' + e.Message );
      end;
    end;
  finally
    FreeAndNil(jpg);
  end;

と書ける(exit は無いが、当該 except on 以降の処理がないので、外側に抜けて finally へ流れる)、という意味ですか?

この場合は、

    except
      on e: Exception do
      begin
        ShowMessage( '[Err] ' + e.Message );
      end;
    end;
    FreeAndNil(jpg);

と同じですから、やはり finally の意味はありません。

# そもそも、必須の事後処理が必要なコードにおいて、例外処理中で exit するなんて(ふつうは)まずないですが。

言いたいことは伝わってますかね…?


finally  2005-12-04 19:19:47  No: 19032

あむさんはどうもfinallyの意味がわかっていないようですね。
>であればこの場合、except 内で exit してますから、当然 finally の処理は実行されずに exit してしまいます。(finally の意味がない。というか、そもそもおかしい。)
except内でexitしてもfinally節は実行されます。
またtryの中でexitしてもfinally節は実行されます。


あむ  2005-12-04 22:28:22  No: 19033

finally さん、ありがとうございます。

なるほど!  あらためて実験してみて目から鱗です。
おっしゃるとおり、finally の仕様について、どうやら大きな誤解をしていたようです。

実験検証に組んでいたコードに(理解に苦しむ)欠陥があり、今まで気づかずにいました。

ご指摘いただいた動作は、以下のコードで一目瞭然でした。

var
  res: Integer;
  pp: String;
begin
  pp := 'a';

  try
    try
      showMessage( '--> pass 1' );
      res := StrToInt(pp);
      showMessage( '--> pass 2' );
    except
      on e: Exception do begin
        showMessage( '--> pass 3: ' + e.Message );
        exit;
        showMessage( '--> pass 4: ' + e.Message );
      end;
    end;
  finally
    showMessage( '--> pass 5' );
  end;
  showMessage( '--> pass 6' );
end;

これで、「ふつうは」さんが言われていたことも理解できました。
大変失礼しました。m(__)m

ちなみに、今まで使っていた検証用のコードとは、以下のものでした。

var
  res: Integer;
  i: Integer;
begin
  i := 0;

  try
    try
      showMessage( '--> pass 1' );
      res := 1 div i;
      showMessage( '--> pass 2' );
    except
      on e: Exception do begin
        showMessage( '--> pass 3: ' + e.Message );
        exit;
        showMessage( '--> pass 4: ' + e.Message );
      end;
    end;
  finally
    showMessage( '--> pass 5' );
  end;
  showMessage( '--> pass 6' );
end;

EDivByZero の例外って、計算した瞬間には発生しなくて、その結果(上記で言えば res の値)を参照した時に初めて発生するんですね。
なので、上記のコードでは、実は例外は発生せずに正常終了するのでした。(@_@)
これっていいのか??


finally  2005-12-04 23:04:58  No: 19034

>EDivByZero の例外って、計算した瞬間には発生しなくて、その結果(上記で言えば res の値)を参照した時に初めて発生するんですね。

いいえすぐに発生します。

>なので、上記のコードでは、実は例外は発生せずに正常終了するのでした。(@_@)
>これっていいのか??

実は上記のコードでは resが必要とされていないのでコンパイラが最適化によって
>res := 1 div i;
の行がコンパイルされていません。
参照するとその行がコンパイルされるのでエラーが発生します。


あむ  2005-12-05 01:44:14  No: 19035

> 実は上記のコードでは resが必要とされていないのでコンパイラが最適化によって
> >res := 1 div i;
> の行がコンパイルされていません。

ほぉー! またもや目から鱗が。。

なるほど。最適化を外したらコードのとおりに動きました。
いやはや、勉強になります。m(__)m


あむ  2005-12-05 17:19:59  No: 19036

とりあえずこれで一旦クローズさせて頂きます。
ありがとうございました。


ふつうは  2005-12-05 17:47:59  No: 19037

解決をチェックします。


@1  2005-12-19 07:02:21  No: 19038

例外をはじめて翻訳した人がもっと別な用語にすればこんなに混乱し無いと
いと思うのですが、
ただの構造文だと思って使った法がいいと思うのですが。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加