ガベージコレクションされた言語のオブジェクトデストラクターパラダイムがなぜ普及しないのですか?


27

ガベージコレクションされた言語設計に関する決定についての洞察を探しています。おそらく、言語の専門家が私を啓発できるでしょうか?私はC ++のバックグラウンドを持っているので、この領域は私を困惑させます。

Ruby、Javascript / ES6 / ES7、Actionscript、LuaなどのOOPyオブジェクトをサポートするほとんどすべてのガベージコレクション言語は、デストラクタ/ファイナライズパラダイムを完全に省略しているようです。Pythonは、そのclass __del__()メソッドを持つ唯一のものであるようです。どうしてこれなの?オブジェクトのデストラクタ/ファイナライズメソッドの効果的な実装を妨げる自動ガベージコレクションを使用する言語には、機能的/理論的な制限がありますか?

これらの言語がメモリを管理する価値のある唯一のリソースと見なしていることは非常に欠けていると思います。ソケット、ファイルハンドル、アプリケーションの状態はどうですか?オブジェクトファイナライズの非メモリリソースと状態をクリーンアップするカスタムロジックを実装する機能がない場合、アプリケーションにカスタムmyObject.destroy()スタイルの呼び出しを散らかし、クリーンアップロジックを「クラス」の外に配置し、カプセル化を中断し、 gcによって自動的に処理されるのではなく、人的エラーによるリソースリークへのアプリケーション。

これらの言語がオブジェクト破棄のカスタムロジックを実行する方法を持たないことにつながる言語設計の決定は何ですか?正当な理由があると想像しなければなりません。これらの言語がオブジェクトの破壊/最終化をサポートしなくなった技術的および理論的な決定をよりよく理解したいと思います。

更新:

おそらく私の質問を表現するより良い方法:

なぜ言語には、カスタムインスタンス化(コンストラクター)とともにクラスまたはクラスに似た構造を持つオブジェクトインスタンスの組み込みコンセプトがありますが、完全に破棄/ファイナライズ機能を省略しますか?自動ガベージコレクションを提供する言語は、オブジェクトが使用されなくなったときに100%の確実性でわかるように、オブジェクトの破壊/最終化をサポートする主要な候補のようです。しかし、これらの言語のほとんどはサポートしていません。

私は、デストラクタが呼び出されることは決してないだろうとは思わない。それは、gcsが回避するように設計されているコアメモリリークだからだ。デストラクタ/ファイナライザは、将来の不確定な時間まで呼び出されないかもしれないが、JavaまたはPythonが機能をサポートするのを止めなかったという議論を見ることができました。

オブジェクトのファイナライズをサポートしない理由は、言語設計の中核となる理由は何ですか?


9
たぶんfinalize/ destroyは嘘ですか?実行される保証はありません。そして、たとえそうだとしても、いつ(自動ガベージコレクションが行われたか)がわからず、必要なコンテキストがまだ残っている(既に収集されている可能性がある)したがって、他の方法で一貫した状態を確保する方が安全であり、プログラマーにそうすることを強制したい場合があります。
ラファエル

1
この質問は境界線外トピックだと思います。それは私たちが楽しませたい種類のプログラミング言語のデザインの質問ですか、それともプログラミングを指向したサイトの質問ですか?コミュニティ投票してください。
ラファエル

14
PL設計では良い質問です。
アンドレイバウアー

3
これは実際には静的/動的な区別ではありません。多くの静的言語にはファイナライザーがありません。実際、ファイナライザーを備えた言語は少数派ではありませんか?
アンドレイバウアー

1
ここに質問があると思います...用語をもう少し定義した方が良いでしょう。javaには、オブジェクトの破壊ではなくメソッドの終了に結び付けられたfinallyブロックがあります。リソースを処理する他の方法もあります。たとえば、javaでは、接続プールは未使用の[x]時間の接続を処理し、それらを再利用できます。エレガントではありませんが、動作します。あなたの質問への答えの一部は、ガベージコレクションはおおむね非決定的であり、瞬間的なプロセスではなく、使用されなくなったオブジェクトではなく、メモリ制約/トリガーされた上限によって駆動されるということです。
vzn

回答:


10

オブジェクトがリソースをクリーンアップする方法を知っている、あなたが話しているパターンは、3つの関連するカテゴリーに分類されます。デストラクタファイナライザを混同しないでください-ガベージコレクションに関連するのは1つだけです。

  • ファイナライザパターン:クリーンアップメソッドが自動的に呼び出され、プログラマによって定義され、自動的に宣言しました。

    ファイナライザは、ガベージコレクタによって割り当て解除の前に自動的に呼び出されます。この用語は、使用されるガベージコレクションアルゴリズムがオブジェクトのライフサイクルを決定できる場合に適用されます。

  • デストラクタパターン:クリーンアップの方法は、自動的にだけ呼ばれることもあり、プログラマによって定義され、自動的に宣言しました。

    デストラクタは、スタックに割り当てられたオブジェクトに対して自動的に呼び出すことができます(オブジェクトの有効期間は決定的であるため)が、ヒープに割り当てられたオブジェクトのすべての実行パスで明示的に呼び出す必要があります(オブジェクトの有効期間は非決定的であるため)。

  • ディスポーザーパターン:クリーンアップ方法が定義され、プログラマによって呼び出され、宣言しました。

    プログラマーはディスポーザブルメソッドを作成し、それを自分で呼び出します-これがカスタムmyObject.destroy()メソッドに該当する場所です。廃棄が絶対に必要な場合は、可能なすべての実行パスでディスポーザを呼び出す必要があります。

ファイナライザーは、あなたが探しているドロイドです。

ファイナライザパターン(質問で尋ねているパターン)は、オブジェクトをシステムリソース(ソケット、ファイル記述子など)に関連付けて、ガベージコレクタによる相互再生のためのメカニズムです。ただし、ファイナライザは基本的に、使用中のガベージコレクションアルゴリズムに左右されます。

あなたのこの仮定を考慮してください:

自動ガベージコレクションを提供する言語...オブジェクトが使用されなくなったときに100%確実に認識します。

技術的に偽(ありがとう、@ babou)。ガベージコレクションは基本的にオブジェクトではなくメモリに関するものです。収集アルゴリズムがオブジェクトのメモリが使用されなくなったことを認識するかどうかは、アルゴリズムと(おそらく)オブジェクトの相互参照方法に依存します。2種類のランタイムガベージコレクターについて説明しましょう。これらを基本的な手法に変更および拡張するには、多くの方法があります。

  1. GCをトレースします。 これらのトレースメモリは、オブジェクトではありません。拡張しない限り、メモリからオブジェクトへの後方参照を維持しません。拡張しない限り、これらのGCは、いつメモリに到達できないかを知っていても、オブジェクトをいつファイナライズできるかを知りません。したがって、ファイナライザの呼び出しは保証されません。

  2. 参照カウントGC。これらはオブジェクトを使用してメモリを追跡します。参照の有向グラフを使用して、オブジェクトの到達可能性をモデル化します。オブジェクト参照グラフにサイクルがある場合、サイクル内のすべてのオブジェクトにファイナライザーが呼び出されることはありません(明らかにプログラム終了まで)。繰り返しますが、ファイナライザの呼び出しは保証されていません。

TLDR

ガベージコレクションは難しく、多様です。プログラムの終了前にファイナライザの呼び出しを保証することはできません。


これは静的ではなく動的ではないことは正しいです。これは、ガベージコレクションされた言語の問題です。ガベージコレクションは複雑な問題であり、考慮すべき多くのエッジケースがあるため、おそらく主な理由です(たとえば、ロジックロジックfinalize()により、クリーンアップされているオブジェクトが再び参照される場合はどうなりますか?)。ただし、プログラムの終了前にファイナライザが呼び出されることを保証できなかったからといって、Javaがファイナライザをサポートするのを止めませんでした。あなたの答えが間違っていると言っていない、たぶん不完全です。まだ非常に良い投稿です。ありがとうございました。
dbcb

フィードバックをお寄せいただきありがとうございます。ここに私の答えを完成させる試みがあります:ファイナライザーを明示的に省略することにより、言語はユーザーに自分のリソースを管理させる。多くのタイプの問題では、それはおそらく不利です。私はファイナライザの力を持っているので、個人的に、私は、Javaの選択を好む書いて、自分のディスポーザーを使用してから私を止めるものは何もありません。Javaのことわざ「ねえ、プログラマー。あなたはバカじゃないので、ここにファイナライザーがあります。注意してください。」
kdbanman

1
これがガベージコレクションされた言語を扱っていることを反映するために、元の質問を更新しました。あなたの答えを受け入れます。答えてくれてありがとう。
dbcb

お力になれて、嬉しいです。コメントを明確にすることで回答が明確になりましたか?
kdbanman

2
それは良いです。私にとっての本当の答えは、認識された価値が機能の実装の問題を上回らないため、言語はそれを実装しないことを選択するということです。(JavaとPythonが示すように)不可能ではありませんが、多くの言語が選択しないというトレードオフがあります。
dbcb

5

一言で言えば

ファイナライズは、ガベージコレクターによって処理される単純な問題ではありません。参照カウントGCでの使用は簡単ですが、このGCファミリは不完全な場合が多く、一部のオブジェクトと構造の破壊とファイナライズの明示的なトリガーによってメモリリークを補償する必要があります。ガベージコレクターのトレースははるかに効果的ですが、未使用のメモリを識別するのとは対照的に、ファイナライズおよび破棄するオブジェクトを識別するのがはるかに難しくなります。したがって、時間とスペースのコスト、および実装。

前書き

あなたが求めているのは、コメントで示されているように、ガベージコレクションされた言語がガベージコレクションプロセス内で破棄/最終化を自動的に処理しない理由だと思います。

これらの言語がメモリを管理する価値のある唯一のリソースと見なしていることは非常に欠けていると思います。ソケット、ファイルハンドル、アプリケーションの状態はどうですか?

私はkdbanmanによって与えられた受け入れられた答えに同意しません。参照カウントに強く偏っているものの、事実はほとんど正しいと述べていますが、質問で不平を言っている状況を適切に説明しているとは思いません。

その答えで開発された用語が多くの問題であるとは思わず、物事を混乱させる可能性が高くなります。実際、提示されているように、用語の大部分は、手順の実行ではなく、手順のアクティブ化方法によって決定されます。ポイントは、すべての場合において、不要なオブジェクトを何らかのクリーンアッププロセスでファイナライズし、使用していたリソースを解放する必要があるということです。メモリはそれらの1つにすぎません。理想的には、ガベージコレクターを使用して、オブジェクトが使用されなくなったときにすべて自動的に実行する必要があります。実際には、GCが欠落しているか不足している可能性があります。これは、ファイナライズおよびレクラメーションのプログラムによる明示的なトリガーによって補償されます。

プログラムによる明示的なトリガーは、使用中のオブジェクトが明示的に終了されている場合、プログラミングエラーの分析が困難になる可能性があるため、問題です。

したがって、自動ガベージコレクションに依存してリソースを再利用することをお勧めします。ただし、次の2つの問題があります。

  • 一部のガベージコレクション手法では、リソースの完全な再利用を妨げるメモリリークが許可されます。これは、参照カウントGCでよく知られていますが、一部のデータ組織を注意せずに使用する場合、他のGCテクニックで表示される場合があります(ここでは説明しません)。

  • GCテクニックは使用されなくなったメモリリソースの識別に優れている場合がありますが、そこに含まれるオブジェクトのファイナライズは単純ではない可能性があります。

最後に、忘れられがちな重要な点は、適切なフックが提供され、GCサイクルのコストが価値があると考えられる場合、メモリ不足だけでなく、あらゆるものによってGCサイクルがトリガーされることです。したがって、何らかのリソースが不足しているときに、一部のリソースを解放することを期待して、GCを開始してもまったく問題ありません。

参照カウントガベージコレクター

参照カウントは、サイクルを適切に処理しない弱いガベージコレクション手法です。確かに、メモリを回収するのが弱いという理由だけで、古い構造を破壊し、他のリソースを回収することに弱いでしょう。ただし、参照カウントガベージコレクター(GC)でファイナライザーを使用すると最も簡単に使用できます。参照カウントGCは、参照カウントが0になったときに構造を再利用します。または動的に。したがって、適切なファイナライザを適用し、すべてのポイントされたオブジェクトでプロセスを再帰的に呼び出した後(おそらくファイナライズプロシージャを介して)、メモリを再利用できます。

要するに、ファイナライズはRef Counting GCを使用して簡単に実装できますが、実際には循環構造に起因するそのGCの「不完全性」に悩まされます。つまり、参照カウントを使用すると、メモリは、ソケット、ファイルハンドルなどの他のリソースとまったく同じように不十分に管理されます。

実際、Ref Count GCがループ構造を再利用できないことは(一般的に)メモリリークと見なされる場合があります。すべてのGCがメモリリークを回避することは期待できません。これは、GCアルゴリズムと、動的に利用可能な型構造情報(たとえば、保守的なGC)に依存します 。

ガベージコレクターのトレース

このようなリークのないGCのより強力なファミリは、明確に識別されたルートポインタから開始して、メモリのライブ部分を探索するトレースファミリです。このトレースプロセスでアクセスされないメモリのすべての部分(実際にはさまざまな方法で分解できますが、単純化する必要があります)は、メモリの未使用部分であり、再利用できます1。これらのコレクターは、プログラムが何をするかに関係なく、プログラムがアクセスできなくなったすべてのメモリー部分を再利用します。循環構造を再利用し、より高度なGCは、このパラダイムのバリエーションに基づいており、時には高度に洗練されています。場合によっては参照カウントと組み合わせて、その弱点を補うことができます。

問題は、あなたの声明(質問の最後)です:

自動ガベージコレクションを提供する言語は、オブジェクトが使用されなくなったときに100%の確実性でわかるように、オブジェクトの破壊/最終化をサポートする主要な候補のようです。

コレクタのトレースでは技術的に正しくありません

100%の確実性で知られているのは、メモリのどの部分が使用されなくなったかです。(より正確には、プログラムのロジックに従って使用できなくなった一部の部分は、プログラム内にそれらへの無意味なポインターがある場合、まだ使用中と見なされるため、アクセスできなくなっていると言われるべきですデータ。)しかし、メモリのこれらの現在未使用の部分に格納されている未使用のオブジェクトを知るには、さらなる処理と適切な構造が必要です。これは、プログラムがメモリのこれらの部分に接続されていないため、プログラムの既知の情報から判断することはできません。

したがって、ガベージコレクションのパスの後、使用されなくなったオブジェクトを含むメモリのフラグメントが残りますが、正しいファイナライズを適用するためにこれらのオブジェクトが何であるかを先験的に知る方法はありません。さらに、トレースコレクターがマークアンドスイープタイプの場合、フラグメントの一部には、以前のGCパスで既にファイナライズされているが、フラグメント化の理由で使用されなかったオブジェクトが含まれている可能性があります。ただし、これは拡張明示的な型指定を使用して処理できます。

単純なコレクターはこれらのメモリの断片を回収するだけで、苦労することなく、ファイナライズには特定のパスを使用してその未使用メモリを探索し、そこに含まれるオブジェクトを識別し、ファイナライズ手順を適用します。ただし、そのような調査では、そこに格納されているオブジェクトのタイプを決定する必要があり、適切なファイナライズがある場合は、タイプ決定も必要です。

そのため、GC時間の追加コスト(追加パス)と、場合によっては追加のメモリコストにより、そのパス中にさまざまな手法で適切な型情報を使用できるようになります。時間とスペースのオーバーヘッドはすべてのオブジェクトに関係する可能性がありますが、少数のオブジェクトのみをファイナライズしたい場合が多いため、これらのコストは重要です。

もう1つのポイントは、GCの実行だけでなく、時間とスペースのオーバーヘッドがプログラムコードの実行に関係する可能性があることです。

私はあなたがリストした言語の多くの詳細を知らないので、特定の問題を指してより正確な答えを出すことはできません。Cの場合、タイピングは保守的なコレクターの開発につながる非常に難しい問題です。私の推測では、これはC ++にも影響しますが、私はC ++の専門家ではありません。これは、保守的なGCに関する多くの研究を行ったハンス・ベームによって確認されているようです。保守的なGCでは、データの正確な型情報が不足している可能性があるため、未使用のメモリをすべて体系的に再利用できません。同じ理由で、ファイナライズ手順を体系的に適用することはできません。

そのため、いくつかの言語からわかるように、あなたが求めていることを行うことができます。しかし、無料ではありません。言語とその実装によっては、機能を使用しない場合でもコストがかかる場合があります。これらの問題に対処するには、さまざまな手法とトレードオフを考慮することができますが、それは合理的なサイズの答えの範囲を超えています。

1-これは、トレースコレクションの抽象的な表現(コピーとマークアンドスイープGCの両方を含む)です。トレースコレクターのタイプによって異なり、コピーまたはマークのどちらであるかによって、メモリの未使用部分の探索は異なります。スイープが使用されます。


ガベージコレクションについて多くの詳細を説明します。しかし、あなたの答えは実際には私の意見と一致しません-あなたの要約と私のTLDRは本質的に同じことを言っています。そして、それが価値があるものとして、私の答えは「強いバイアス」ではなく、参照カウントGCを例として使用しています。
kdbanman

もっと徹底的に読んだ後、私は意見の相違を見る。それに応じて編集します。また、私の用語は明確にすることでした。問題は、ファイナライザとデストラクタを混同することであり、ディスポーザについても同じように言及していました。適切な言葉を広める価値はあります。
kdbanman

@kdbanman難しさは、あなたの答えが参照として立っていたので、私はあなたの両方に話しかけることでした。ref countは典型的な例として使用することはできません。これは弱いGCであり、言語ではめったに使用されない(OPが引用する言語を確認してください)ほとんどの場合、トレースコレクターが使用されます。しかし、ファイナライザーはそれらをフックするのが困難です。なぜなら、死にかけているオブジェクトは知られていないからです(正しいと考えるステートメントに反して)。静的型付けと動的型付けの区別は重要ではありません。データストアの動的型付けが不可欠です。
babou

@kdbanman用語に関しては、さまざまな状況に対応しているため、一般に役立ちます。しかし、ここでは、GCにファイナライズを転送することに関する質問であるため、役に立ちません。基本的なGCは破壊のみを行うことになっています。必要なのは、他のリソースを回収したり、私が呼ぶオブジェクトテーブルを更新するなど、getting memory recycled私が呼ぶものを区別し、そのreclamation前に何らかのクリーンアップを行う用語ですfinalization。これらは関連する問題のように思われましたが、あなたの専門用語の中で私が知らなかった点を見落としているかもしれません。
babou

1
おかげで@kdbanman、バブー。良い議論。どちらの投稿も同様のポイントをカバーしていると思います。両方が指摘しているように、核となる問題は、言語のランタイムで使用されるガベージコレクターのカテゴリであると思われます。私はこの記事を見つけましたが、それは私にとっていくつかの誤解を解消します。より堅牢なgcsは、低レベルのrawメモリのみを処理するため、高レベルのオブジェクトタイプはgcに対して不透明になります。メモリ内部の知識がなければ、gcはオブジェクトを破壊できません。これはあなたの結論のようです。
dbcb

4

オブジェクトデストラクタパターンは、システムプログラミングにおけるエラー処理の基本ですが、ガベージコレクションとは関係ありません。むしろ、オブジェクトの有効期間をスコープに一致させることに関係しており、ファーストクラスの機能を持つ任意の言語で実装/使用できます。

例(擬似コード)。Posixファイル記述子タイプのような「生ファイル」タイプがあるとします。4つの基本的な操作はありますが、open()close()read()write()。常にクリーンアップする「安全な」ファイルタイプを実装する必要があります。(つまり、自動コンストラクタとデストラクタがあります。)

私は、私たちの言語はで例外処理を持っていると仮定しますthrowtryfinally(あなたがあなたのタイプのユーザーがエラーを示すために特別な値を返す規律を設定することができ例外処理なしの言語で。)

作業を行う機能を受け入れる機能を設定します。ワーカー関数は、1つの引数(「安全な」ファイルへのハンドル)を受け入れます。

with_file_opened_for_read (string:   filename,
                           function: worker_function(safe_file f)):
  raw_file rf = open(filename, O_RDONLY)
  if rf == error:
    throw File_Open_Error

  try:
    worker_function(rf)
  finally:
    close(rf)

あなたはまたの実装を提供read()し、write()ためsafe_file(だけ呼び出すことraw_file read()としますwrite())。これで、ユーザーは次のsafe_fileようなタイプを使用します。

...
with_file_opened_for_read ("myfile.txt",
                           anonymous_function(safe_file f):
                             mytext = read(f)
                             ... (including perhaps throwing an error)
                          )

C ++デストラクタは、実際にはtry-finallyブロックの単なる構文糖です。ここでやったことのほとんどはsafe_file、コンストラクタとデストラクタを持つC ++ クラスをコンパイルするものを変換することです。C ++にはfinally例外がないことに注意してください。具体的には、Stroustrupは明示的なデストラクタを使用する方が構文的には優れていると感じたためです(そして、言語に匿名関数を追加する前にそれを言語に導入しました)。

(これは、人々が長年Lispのような言語でエラー処理を行ってきた方法の1つを単純化したものです。最初に1980年代後半または1990年代初頭に遭遇したと思いますが、どこにあるか覚えていません。)


これは、C ++のスタックベースのデストラクタパターンの内部について説明していますが、ガベージコレクション言語がこのような機能を実装しない理由を説明していません。これはガベージコレクションとは関係ないことは正しいかもしれませんが、ガベージコレクション言語では困難または非効率的であると思われる一般的なオブジェクトの破棄/最終化に関連しています。したがって、一般的な破壊がサポートされていない場合、スタックベースの破壊も同様に省略されるようです。
dbcb

私が最初に言ったように:第一級関数(またはファーストクラスの機能のいくつかの近似値)を持つ任意のごみ収集の言語あなたのような「防弾」のインターフェイスを提供することができますsafe_fileし、with_file_opened_for_read(それがスコープ外になったときに自分自身を閉じて、オブジェクトを)。それは重要なことです。コンストラクターと同じ構文を持たないことは無関係です。Lisp、Scheme、Java、Scala、Go、Haskell、Rust、Javascript、Clojureはすべて十分なファーストクラス機能をサポートしているため、同じ便利な機能を提供するためにデストラクタを必要としません。
さまようロジック

私はあなたが言っていることを見ると思う。言語は、デストラクタのような機能を手動で実装するための基本的な構成要素(try / catch / finally、ファーストクラス関数など)を提供するため、デストラクタは不要ですか?簡単にするために、いくつかの言語がそのルートを取っているのが見えました。しかし、それがリストされているすべての言語の主な理由である可能性は低いと思われますが、おそらくそれがその理由です。たぶん、私はC ++デストラクタを愛し、他の誰も本当に気にしていない広大な少数派にいるので、ほとんどの言語がデストラクタを実装していないのは非常に理由かもしれません。彼らは気にしません。
dbcb

2

これは質問に対する完全な回答ではありませんが、他の回答やコメントでカバーされていないいくつかの所見を追加したいと思いました。

  1. この質問は、それ自体が制限されているSimulaスタイルのオブジェクト指向言語について話していると暗黙的に仮定しています。ほとんどの言語では、オブジェクトがある言語であっても、すべてがオブジェクトであるとは限りません。デストラクタを実装する機械は、すべての言語実装者が喜んで支払うわけではないコストを課します。

  2. C ++には、破壊順序に関する暗黙的な保証がいくつかあります。たとえば、ツリーのようなデータ構造がある場合、子は親の前に破棄されます。これはGC化された言語には当てはまらないため、階層リソースは予測できない順序でリリースされる可能性があります。メモリ以外のリソースの場合、これは重要です。


2

最も人気のある2つのGCフレームワーク(Javaと.NET)が設計されたとき、著者は、他の形式のリソース管理の必要性を回避するために、ファイナライズが十分に機能すると期待したと思います。100%信頼性の高い確定的なリソース管理に対応するために必要なすべての機能が必要ない場合、言語およびフレームワークの設計の多くの側面を大幅に簡素化できます。C ++では、次の概念を区別する必要があります。

  1. 参照の所有者によって排他的に所有され、所有者が知らないポインター/参照によって識別されないオブジェクトを識別するポインター/参照。

  2. 誰もが所有していない共有可能なオブジェクトを識別するポインター/参照。

  3. 参照の所有者によって排他的に所有されているが、「ビュー」を介してアクセスできるオブジェクトを識別するポインター/参照は、所有者が追跡する方法を持ちません。

  4. 他の誰かが所有するオブジェクトのビューを提供するオブジェクトを識別するポインター/参照。

GC言語/フレームワークがリソース管理を心配する必要がない場合、上記のすべてを単一の種類の参照に置き換えることができます。

私は、ファイナライズによって他の形式のリソース管理の必要性がなくなるという考えはナイーブですが、そのような期待が当時妥当だったかどうかにかかわらず、ファイナライズが提供するよりも正確なリソース管理を必要とする多くのケースがあることを歴史は示しています。言語/フレームワークレベルで所有権を認める見返りは、コストを正当化するのに十分だと思います(複雑さはどこかに存在する必要があり、言語/フレームワークに移すとユーザーコードが簡素化されます)設計は、単一の「種類」の参照を持っていることで利点があります。これは、言語/フレームワークがリソースのクリーンアップの問題に依存しない場合にのみ機能します。


2

ガベージコレクションされた言語のオブジェクトデストラクターパラダイムがなぜ普及しないのですか?

私はC ++のバックグラウンドを持っているので、この領域は私を困惑させます。

C ++のデストラクタは、実際には2つの ことを組み合わせて実行します。RAMとリソースIDを解放します。

他の言語では、GCがRAMの解放を担当し、別の言語機能がリソースIDの解放を担当することで、これらの懸念を分離しています。

これらの言語がメモリを管理する価値のある唯一のリソースと見なしていることは非常に欠けていると思います。

これがGCのすべてです。彼らはただ一つのことをしません、そして、それはあなたがメモリを使い果たさないことを確実にすることです。RAMが無限の場合、GCが存在する本当の理由がなくなったため、すべてのGCは廃止されます。

ソケット、ファイルハンドル、アプリケーションの状態はどうですか?

言語は、以下によってリソースIDを解放するさまざまな方法を提供できます。

  • .CloseOrDispose()コード全体に散在する手動

  • マニュアル.CloseOrDispose()finallyブロック」内に散在するマニュアル

  • マニュアル「リソースID・ブロック」(すなわちusingwithtry-with-リソース自動化、など).CloseOrDispose()ブロックがされた後に行わ

  • .CloseOrDispose()ブロックが完了したに自動化される保証された「リソースIDブロック」

多くの言語では、リソースの誤った管理の機会を作り出す手動の(保証ではなく)メカニズムを使用しています。次のシンプルなNodeJSコードをご覧ください。

require('fs').openSync('file1.txt', 'w');
// forget to .closeSync the opened file

..開いているファイルを閉じるのをプログラマが忘れた場所。

プログラムが実行されている限り、開かれたファイルはあいまいになります。これは、HxDを使用してファイルを開こうとし、実行できないことを確認することで簡単に確認できます。

ここに画像の説明を入力してください

C ++デストラクタ内でリソースIDを解放することも保証されていません。RAIIは保証された「リソースIDブロック」のように動作しますが、「リソースIDブロック」とは異なり、C ++言語はRAIIブロックを提供するオブジェクトのリークを停止しないため、RAIIブロックは実行されません。


Ruby、Javascript / ES6 / ES7、Actionscript、LuaなどのOOPyオブジェクトをサポートするほとんどすべてのガベージコレクション言語は、デストラクタ/ファイナライズパラダイムを完全に省略しているようです。Pythonは、そのクラス__del__()メソッドを持つ唯一のものであるようです。どうしてこれなの?

上記のように、他の方法を使用してリソースIDを管理するためです。

これらの言語がオブジェクト破棄のカスタムロジックを実行する方法を持たないことにつながる言語設計の決定は何ですか?

上記のように、他の方法を使用してリソースIDを管理するためです。

なぜ言語には、カスタムインスタンス化(コンストラクター)とともにクラスまたはクラスに似た構造を持つオブジェクトインスタンスの組み込みコンセプトがありますが、完全に破棄/ファイナライズ機能を省略しますか?

上記のように、他の方法を使用してリソースIDを管理するためです。

デストラクタ/ファイナライザは、将来の不確定な時間まで呼び出されないかもしれないが、JavaまたはPythonが機能をサポートするのを止めなかったという議論を見ることができました。

Javaにはデストラクタがありません。

Javaのドキュメントは言及します

ただし、ファイナライズの通常の目的は、オブジェクトが取り消せないように破棄される前にクリーンアップアクションを実行することです。たとえば、入出力接続を表すオブジェクトのfinalizeメソッドは、明示的なI / Oトランザクションを実行して、オブジェクトが永久に破棄される前に接続を切断する場合があります。

..ただし、リソースID管理コードを内部に配置することObject.finalizerは、主にアンチパターンと見なされます(cf.)。代わりに、これらのコードは呼び出しサイトで作成する必要があります。

アンチパターンを使用する人々にとって、正当な理由は、コールサイトでリソースIDをリリースするのを忘れている可能性があるということです。したがって、念のため、ファイナライザで再度実行します。

オブジェクトのファイナライズをサポートしない理由は、言語設計の中核となる理由は何ですか?

オブジェクトへの強い参照がなくなってからメモリがGCによって回収されるまでの間にコードを実行するため、ファイナライザーのユースケースは多くありません。

可能なユースケースは、GCによってオブジェクトが収集される時間と、オブジェクトへの強い参照がなくなった時間のログを保持したい場合などです。

finalize() {
    Log(TimeNow() + ". Obj " + toString() + " is going to be memory-collected soon!"); // "soon"
}

-1

これについては、ドクタードブスwrt c ++で、デストラクタが実装されている言語ではデストラクタに問題があると主張する、より一般的な考えを持つrefが見つかりました。ここでの大まかな考えは、デストラクタの主な目的はメモリの割り当て解除を処理することであり、それを正しく達成することは難しいようです。メモリは区分的に割り当てられますが、異なるオブジェクトが接続され、割り当て解除の責任/境界がそれほど明確ではありません。

ガベージコレクターのこのソリューションは数年前に進化しましたが、ガベージコレクションはメソッド出口でスコープから消えるオブジェクト(実装するのが難しい概念的アイデア)に基づいているのではなく、定期的に、ある程度非決定的に実行されるコレクターに基づいていますアプリが「メモリ不足」(メモリ不足)を経験したとき。

言い換えれば、「新しく使用されていないオブジェクト」という単なる人間の概念は、実際には、オブジェクトが「即座に」使用されないという意味で誤解を招く抽象化です。未使用のオブジェクトは、オブジェクト参照グラフを走査するガベージコレクションアルゴリズムを実行することによってのみ「発見」でき、最高のパフォーマンスを発揮するアルゴリズムは断続的に実行されます。

未使用のオブジェクトをほぼ瞬時に識別できる、より良いガベージコレクションアルゴリズムが発見されるのを待っている可能性があります。これにより、一貫したデストラクタ呼び出しコードにつながる可能性があります。

ファイルや接続などのリソース管理領域の解決策は、その使用を処理しようとするオブジェクト「マネージャー」を持つことのようです。


2
興味深い発見。ありがとう。著者の議論は、クラスが適切なコピーコンストラクターを持たない値でクラスインスタンスを渡すために間違ったタイミングで呼び出されるデストラクタに基づいています(これは実際の問題です)。ただし、このシナリオは、すべてではないにしてもほとんどの現代の動的言語には存在しません。これは、すべてが参照によって渡されるため、著者の状況を回避するためです。これは興味深い視点ですが、ほとんどのガベージコレクション言語がデストラクタ/ファイナライゼーション機能を省略することを選択した理由を説明しているとは思いません。
dbcb

2
この回答は、ドブ博士の記事を誤って伝えています。この記事は、デストラクタが一般的に問題があると主張していません。記事では実際に次のように主張しています。メモリ管理プリミティブは、gotoステートメントのようなものです。なぜなら、これらは単純でありながら強力すぎるからです。gotoステートメントが「適切に制限された制御構造」に最適にカプセル化されるのと同じ方法で(Dijktsraを参照)、メモリ管理プリミティブは「適切に制限されたデータ構造」に最適にカプセル化されます。デストラクタはこの方向への一歩ですが、十分ではありません。 それが本当かどうかを自分で決めてください。
kdbanman
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.