オブジェクト指向プログラミングでは、dispose パターンはリソース管理のデザイン パターンです。このパターンでは、リソースはオブジェクトによって保持され、言語に応じて通常、、と呼ばれる従来のメソッドを呼び出すことによって解放されます。これにより、オブジェクトが保持しているリソースが解放されます。多くのプログラミング言語では、一般的な状況で dispose メソッドを明示的に呼び出す必要がないようにする
言語構造が提供されています。closedisposefreerelease
Dispose パターンは、実行時に自動ガベージ コレクションが実行される言語で主に使用されます(以下の理由を参照)。
モチベーション
オブジェクトにリソースをラップする
リソースをオブジェクトにラップすることは、カプセル化のオブジェクト指向形式であり、dispose パターンの基礎となります。
リソースは通常、ハンドル(抽象参照) で表され、具体的には整数で表されます。これは、リソースを提供する外部システムと通信するために使用されます。たとえば、ファイルはオペレーティング システム(具体的にはファイル システム) によって提供され、多くのシステムでは、開いているファイルはファイル記述子(ファイルを表す整数) で表されます。
これらのハンドルは、値を変数に格納し、その値をリソースを使用する関数に引数として渡すことで直接使用できます。ただし、ハンドル自体を抽象化し (たとえば、異なるオペレーティング システムでファイルの表現が異なる場合)、追加の補助データをハンドルと共に格納すると便利な場合が多くあります。そのため、ハンドルは他のデータとともにレコード内のフィールドとして格納できます。これが不透明なデータ型の場合、情報の隠蔽が提供され、ユーザーは実際の表現から抽象化されます。
たとえば、C のファイル入出力では、ファイルは 型のオブジェクトFILE(紛らわしいことに「ファイル ハンドル」と呼ばれます。これは言語レベルの抽象化です) で表され、ファイルへの (オペレーティング システムの) ハンドル (ファイル記述子など) と、I/O モード (読み取り、書き込み) やストリーム内の位置などの補助情報を格納します。これらのオブジェクトは、fopen(オブジェクト指向用語では、コンストラクターfclose) を呼び出すことによって作成されます。コンストラクターはリソースを取得してそのポインターを返します。リソースは、オブジェクトへのポインターを呼び出すことによって解放されますFILE。[1]コードでは:
FILE * f = fopen ( filename , mode ); // f で何かを実行します。fclose ( f );
fcloseはパラメータを持つ関数であることに注意してくださいFILE *。オブジェクト指向プログラミングでは、これはPython のようにファイル オブジェクトの
インスタンス メソッドになります。
f = open (ファイル名)
# f で何かする。
f . close ()
これはまさに dispose パターンであり、従来のファイルのオープンとクローズとは構文とコード構造[a]のみが異なります。他のリソースもまったく同じ方法で管理できます。つまり、コンストラクターまたはファクトリーで取得し、明示的なメソッドまたはメソッドで解放しcloseますdispose。
迅速な釈放
リソースを解放することで解決しようとする根本的な問題は、リソースは高価である (たとえば、開いているファイルの数に制限がある) ため、すぐに解放する必要があることです。さらに、特に I/O の場合、すべてのデータが実際に書き込まれたことを確認するためにバッファーをフラッシュするなど、何らかの最終処理作業が必要になることがあります。
リソースが無制限または事実上無制限であり、明示的なファイナライズが必要ない場合、そのリソースを解放することは重要ではありません。実際、短命のプログラムはリソースを明示的に解放しないことがよくあります。実行時間が短いため、リソースを使い果たす可能性は低く、ファイナライズはランタイム システムまたはオペレーティング システムに依存します。
ただし、一般的にはリソースを管理する必要があります (特に、長期間実行されるプログラム、多くのリソースを使用するプログラム、または安全のため、データが書き出されることを確認するため)。明示的な破棄とは、リソースの最終処理と解放が決定論的かつ迅速であることを意味します。つまり、disposeこれらが完了するまでメソッドは完了しません。
明示的な破棄を要求する代わりに、リソース管理をオブジェクトの有効期間に結び付けることもできます。つまり、リソースはオブジェクトの作成時に取得され、オブジェクトの破棄時に解放されます。このアプローチは、リソース取得初期化(RAII) イディオムとして知られており、決定論的なメモリ管理を行う言語 ( C++など) で使用されます。この場合、上記の例では、ファイル オブジェクトの作成時にリソースが取得され、変数のスコープfが終了すると、参照するファイル オブジェクトがf破棄され、この一環としてリソースが解放されます。
RAII は、オブジェクトの有効期間が決定的であることを前提としています。ただし、自動メモリ管理では、オブジェクトの有効期間はプログラマーの懸念事項ではありません。オブジェクトは使用されなくなった後のある時点で破棄されますが、いつ破棄されるかは抽象化されます。確かに、有効期間は決定的ではないことがよくありますが、特に参照カウントが使用されている場合は決定的である可能性があります。実際、場合によっては、オブジェクトがファイナライズされるという保証はありません。プログラムが終了すると、オブジェクトがファイナライズされず、代わりにオペレーティング システムにメモリを再利用させるだけになる場合があります。ファイナライズが必要な場合 (バッファーをフラッシュする場合など)、データが失われる可能性があります。
このように、リソース管理をオブジェクトの有効期間と結び付けないことで、破棄パターンはリソースを迅速に解放し、メモリ管理の実装の柔軟性を実現します。ただし、その代償として、リソースを手動で管理する必要があり、面倒でエラーが発生しやすくなります。
早期退場
Dispose パターンの主な問題は、disposeメソッドが呼び出されない場合、リソースがリークされることです。この問題の一般的な原因は、早期の戻りまたは例外により関数が早期に終了することです。
例えば:
def func (ファイル名):
f = open (ファイル名)
if a :
return x
f . close ()
return y
関数が最初の戻りで戻ると、ファイルは閉じられず、リソースがリークされます。
def func ( filename ):
f = open ( filename )
g ( f ) # f で例外が発生する可能性のある操作を実行します。
f . close ()
介入するコードが例外を発生させると、関数は早期に終了し、ファイルは閉じられないため、リソースがリークされます。
これらは両方ともtry...finally、finally 句が終了時に常に実行されることを保証する構文によって処理できます。
def func ( filename ):
try :
f = open ( filename )
# 何かを実行します。
finally :
f . close ()
より一般的には:
リソースresource = getResource (); try { // リソースが取得されました。リソースを使用してアクションを実行します。... } finally { //例外がスローされた場合でも、リソースを解放します。resource . dispose (); }
この構造は、ブロック内で例外がスローされたかどうかに関係なく、ブロックによってクリーンアップ ロジックの実行が可能になるため、適切な例外安全性try...finallyのために必要です。
finallytry
このアプローチの欠点の 1 つは、プログラマーがブロック内にクリーンアップ コードを明示的に追加する必要があることですfinally。これによりコード サイズが肥大化し、これを怠るとプログラム内でリソースの漏洩が発生します。
言語構成
破棄パターンをより簡潔に安全に使用できるようにするために、いくつかの言語には、同じコード ブロック内で保持および解放されるリソースに対する何らかの組み込みサポートがあります。
C #言語には、インターフェースを実装するオブジェクトのメソッドを自動的に呼び出すusingステートメント[2]があります。
DisposeIDisposable
using ( Resource resource = GetResource ()) { // リソースを使用してアクションを実行します。... }
これは次の式に等しい:
リソースresource = GetResource () try { // リソースを使用してアクションを実行します。... } finally { // リソースが取得されていないか、すでに解放されている可能性がありますif ( resource != null ) (( IDisposable ) resource ). Dispose (); }
同様に、Python言語にはコンテキストマネージャwithオブジェクトで同様の効果を発揮するステートメントがあります。コンテキストマネージャプロトコルでは、 /パターンで発生するコードの重複を防ぐために、ステートメント構造によって自動的に呼び出されるメソッドを実装する必要があります。[3]__enter____exit__withtryfinally
resource_context_manager () をリソースとして 使用: # リソースを使用してアクションを実行します。... # リソースが確実に割り当て解除されるその他のアクションを実行します。...
Java言語はtryJavaバージョン7で-with-resourcesと呼ばれる新しい構文を導入しました。[4]これはAutoCloseableインターフェース(close()メソッドを定義する)を実装するオブジェクトで使用できます。
try ( OutputStream x = new OutputStream (...)) { // x で何かする} catch ( IOException ex ) { // 例外を処理する
// リソースxは自動的に閉じられます
} // try
問題
戻り値や例外がある場合の正しいリソース管理、ヒープベースのリソース管理 (オブジェクトを作成された場所とは異なるスコープで破棄する) という主要な問題以外にも、破棄パターンに関連する複雑さは数多くあります。これらの問題は、RAIIによって大部分が回避されます。ただし、一般的な単純な使用では、これらの複雑さは発生しません。つまり、単一のリソースを取得し、それを使用して何かを実行し、自動的に解放します。
根本的な問題は、リソースを持つことがもはやクラス不変条件ではなくなることです(リソースはオブジェクトの作成から破棄されるまで保持されますが、オブジェクトはこの時点ではまだ有効です)。そのため、オブジェクトがリソースを使用しようとしたときに (たとえば、閉じたファイルから読み取ろうとしたとき)、リソースが利用できない場合があります。つまり、リソースを使用するオブジェクトのすべてのメソッドが失敗する可能性があり、具体的には通常、エラーを返すか例外を発生させます。実際には、これは軽微です。リソースの使用は通常、他の理由でも失敗する可能性があるため (たとえば、ファイルの末尾を超えて読み取ろうとした場合)、これらのメソッドはすでに失敗する可能性があり、リソースがないと、さらに別の失敗の可能性が追加されるだけです。これを実装する標準的な方法は、と呼ばれるブール型フィールドをオブジェクトに追加することです。このフィールドdisposedは によって true に設定されdispose、ガード句ObjectDisposedExceptionによってすべてのメソッド (リソースを使用する) にチェックされ、オブジェクトが破棄されている場合は例外 (.NET など) が発生します。 [5]
disposeさらに、オブジェクトを複数回呼び出すこともできます。これはプログラミング エラー (リソースを保持する各オブジェクトは1 回だけ破棄する必要があります) を示している可能性がありますが、 をべき等にする(つまり、「複数回の呼び出しは 1 回の呼び出しと同じ」)方が単純で堅牢でdisposeあるため、通常は望ましい方法です。 [5]これは、同じブールフィールドを使用して、 の先頭のガード句でそれをチェックすることで簡単に実装できます。その場合、例外を発生させるのではなく、すぐに戻ります。[5] Java は、使い捨て型 (AutoCloseable を実装するもの) と、破棄がべき等である使い捨て型 (サブタイプ Closeable) を区別します。
disposeddispose
継承がある場合の破棄と、リソースを保持するオブジェクトの合成には、破棄/ファイナライズ (デストラクタまたはファイナライザ経由) と類似した問題があります。さらに、破棄パターンは通常、これに対する言語サポートがないため、定型コードが必要です。まず、派生クラスがdispose基本クラスのメソッドをオーバーライドする場合、派生クラスのオーバーライド メソッドは通常dispose、基本クラスで保持されているリソースを適切に解放するために、基本クラスのメソッドを呼び出す必要があります。次に、オブジェクトがリソースを保持する別のオブジェクトと「has a」関係にある場合 (つまり、オブジェクトが、リソースを直接使用する別のオブジェクトを介して間接的にリソースを使用する場合)、間接的に使用するオブジェクトは破棄可能である必要がありますか? これは、関係が所有(オブジェクト合成) であるか、表示(オブジェクト集約) であるか、または単に通信(関連付け) であるかに対応し、両方の規則が見つかります (間接ユーザーはリソースに対して責任を負うか、責任を負わないか)。間接使用がリソースの責任を負っている場合、そのリソースは破棄可能であり、破棄されるときに所有オブジェクトも破棄する必要があります (所有オブジェクトを破棄または終了するのと同様)。
合成 (所有) はカプセル化(使用されるオブジェクトのみを追跡する必要がある) を提供しますが、オブジェクト間にさらに関係がある場合にはかなり複雑になります。一方、集約 (表示) はカプセル化が欠如する代わりにかなり単純になります。.NETでは、リソースの直接のユーザーのみが責任を負うのが慣例です。「型がアンマネージ リソースを直接使用する場合にのみ、IDisposable を実装する必要があります。」[6]詳細とその他の例については、 リソース管理を参照してください。
参照
- オブジェクトの寿命
- リソース取得は初期化です(RAII)
注記
- ^ クラスベースのプログラミングでは、メソッドは明示的なパラメータを取る関数としてではなく、暗黙的なパラメータまたはパラメータを使用してクラス内で定義されます。
thisself
参考文献
- ^ – ベース定義リファレンス、The Single UNIX 仕様、バージョン 4、The Open Group
- ^ Microsoft MSDN: using ステートメント (C# リファレンス)
- ^ Guido van Rossum、 Nick Coghlan (2011 年 6 月 13 日)。「PEP 343: "with" ステートメント」。Python Software Foundation。
- ^ Oracle Java チュートリアル: try-with-resources ステートメント
- ^ abc 「Dispose パターン」。
- ^ 「IDisposable インターフェース」 。2024年 12 月 9 日閲覧。
さらに読む
- Microsoft Developer Network: Dispose パターン
