C++のメモリリークを防ぐフレームワークSymbian OS開発の勘所(4)(2/3 ページ)

» 2007年04月24日 00時00分 公開
[大久保 潤 管理工学研究所,@IT MONOist]

古典的なエラー処理と、その問題

 今回のゴールはSymbian OSにおけるメモリ管理(リソース管理)フレームワークです。このフレームワークの必要性を明らかにするために、メモリ管理(リソース管理)で解決を図ろうとした根源の問題までさかのぼってみましょう。

 Cで関数呼び出しをする際の典型的なシーケンスを考えてみます。


 これを縦軸に時間軸、横軸にコーリングシーケンスを取って配置したものを図3に示します。

コーリングシーケンス 図3 コーリングシーケンス

 何が問題なのかと思われるかもしれません。が、途中の関数が戻り値判定と、それを引き継いで上位にエラーコードを返す処理を怠った場合を考えてみましょう。すると当たり前ですが、上位の関数は不適切な状態のまま処理を継続することになります。要するに不具合の発生です。

 プログラムの不具合や、APIの戻り値に対する誤解や、そのほか不思議な事情により戻り値判定が漏れる可能性が5%あるとすると、ある関数が正しく動作する可能性はその残り95%になります。関数呼び出しは直列システムですから、全体の信頼性はこれらの信頼性の積として求まります。関数呼び出しのネストが10段あるとすると、0.95の10乗となりますから、この場合全体の信頼性は約60%まで低下してしまいます。

 プロジェクトの参加者全員が必ず戻り値判定を適切に行ってくれれば問題ない、といい張る人もいますが本当でしょうか。筆者はこのようなナイーブな態度を「希望的観測に基づくプログラミング」とか「善意に基づくプログラミング」などと呼んでいます。

 障害などの結果を受けてある処理をリトライしようとした場合、通常リトライ制御はユーザーが認識している文脈に近い個所で行う必要があります。トランザクションを破棄してもう1回やり直すなどという処理を、エラーを最初に発見する一番深いレベルの関数に行わせるわけにはいきません。何としてもトランザクションを認識できるトップレベルまで戻ってから、リトライの要否、可否を判定して、処理を継続しなければ。そのためにも必要なエラーを伝えるべきところに確実に伝える仕掛けが必要です。

Symbian OSの提供する例外処理

 これらの問題点を解決するため、現代のプログラミング言語では例外事象(各種障害の検出、サービス範囲外の状態の検出な)を確実に通知するための機構、例外処理というものを支援するのが一般的です。例外処理は事象を通知する側と、それを受ける側の二者から構成されます。

 C++で用意されている構文では、通知側はthrow、受け側はcatchとなっています。ポイントを示します。

  • throwにより例外に関する事象(情報)を投げることができる
  • throwで投げられた例外は、catchで捕まえられるまで関数の呼び出しをさかのぼり続ける
  • そのため例外を気にしたくない関数(いままで戻り値を上位に伝搬するだけだった関数)は引き継ぎ処理から解放される
  • throwからcatchまでの制御フローはコール・リターンとは別

 図3のコーリングシーケンスに、この例外処理を導入したものが図4です。

例外シーケンス 図4 例外シーケンス

 ただしSymbian OSではこの例外処理機能の提供方法が、C++標準とは微妙に異なっています。具体的にはthrowに相当するのがUser::LeaveというAPI、catchに相当するのがTRAPというマクロで提供されています。これはSymbian OSのデザインが行われていたとき、C++には例外処理の機能が標準では入っていなかったことに依ります。

 永らくこれらの内側にはSymbian OSのフレームワークが支援する独自の例外処理機構が潜んでいました。しかしSymbian OSのv9.1からはthrow−catchベースの実装に変わっています。ではUser::Leave−TRAPはどうなるのですか、という質問を受けることがありますが、APIとしてのUser::Leave−TRAPがなくなるわけではありません。ご安心ください。むしろthrow−catchとの混在について明確なルールができるまで、当面の間はUser::Leave−TRAPベースのコードを墨守すべきです。

 例外に関する注意事項を補足しておきます。

一般的なルール:Leave−TRAPをthrow−catchと読み替えても通用する

自分に関係ない例外はTRAPしない
→捕まえた人は処理をする責務が発生する。処理できない例外を捕まえてしまった場合は責任を持って上位層に投げ直す
→90%以上の例外がトップレベルで処理される

例外処理にはコストがかかる
→TRAPと構えるだけでコストが発生する(Leave発生時のみコストが発生するわけではない)
→関数リターンのコストの1000倍から1万倍のコスト

例外を返す可能性がある関数を呼び出した場合、TRAPを行わない限り自分もまた例外を返す可能性を持つことになる。これを「例外伝搬」という

 間違っても現状の関数リターンで返していたエラー情報を、すべて例外に置き換えようなどとは思わないように。

Symbian OSに固有なルール

例外を返す可能性がある関数は、名称の最後にLを付けること
→例外伝搬のケースも忘れずにLを付けること

 L付きルールの適用を忘れた場合、実際の開発プロジェクトでは非常に厳しい指導が行われるはずです。

 さてエラー処理に関する問題の何割かがこのようにシステムレベルで解決されることになりました。しかしメモリ管理にとってはこれこそが問題の始まりだったのです。

Copyright © ITmedia, Inc. All Rights Reserved.