-1

以下は、このトピックに関する現在入手可能な知識から導き出された一連の結論であり、本質的に問題は、これが正しいかどうか、そうでない場合、これらの結論に対する適切な修正は何かということです.

経験豊富な .net 開発者として、私は完全に、すべてのオブジェクト インスタンスがクラウド内のほとんど粒子として存在し、オブジェクト メンバーシップは単にそれらの粒子を相互に関連付けるという考えに完全に慣れています。フレームワーク内のルート オブジェクトへの何らかの参照によってパーティクルまたはパーティクル クラウドをリンクできない場合、それまたはそのメンバーは破棄されます。これは基本的に、参照カウントと、オブジェクトがまだルートへのパスを持っているかどうかの理解を維持するある種のネットワーク分析の結果です。

この構造では、オブジェクトの識別可能なインスタンスを他の多くのオブジェクトから参照 (「所有」) することができ、所有権のネットワークを必要に応じて複雑かつ循環/自己参照にすることができます。

C++ への移行では、メモリ管理のためにこの自由を制限する必要があることがわかります。すべてのオブジェクトには、オブジェクトの有効期間が親によって維持される明確な所有権ツリー​​が必要です。

ライフタイムは、波括弧で囲まれたコード セクション {} 内の一時的な値のように、スコープによって制約される場合もあります。絶対に避けるべきですが、newキーワードを使用し、delete

C++ の新しい標準化では、shared_ptr のようなものは、.net マネージ メモリ モデルに近いものを許可するように設計されているようです。これらが、自己参照的ではあるが接続されていないオブジェクト クラウドを破棄するマネージド メモリと同じ利点を提供するかどうかはわかりません。

例は std::list です。私が知る限り、推奨される戦略は、リスト内のオブジェクト インスタンスは、shared_ptr コンストラクトの利点なしで、リストによってのみ所有され、リスト自体の有効期間によって決定される有効期間を持つ必要があるということです。これにより、一時的に呼び出されるコピー コンストラクター、デストラクタ、または単一の具象型を持つリストを必要とする emplace メソッドの使用が必要になります。別の解決策として、オブジェクトへのポインターを格納し、オブジェクトの有効期間を別の場所で管理する必要がありますが、これには明らかな危険が伴います。これはすべて非常に厄介なようです。

.net では、オブジェクトの有効期間が別の方法で管理されるため、リストは親またはガーディアンでなくてもオブジェクトを参照できます。

パフォーマンスに影響を与えるヒープメモリとスタックメモリには違いがあることは知っていますが、このトピックについて詳しくは知りません。

2 つのシステムに対する私の見方は本質的に正しいですか? そうでない場合、どのような修正を提供できますか? それが本質的に正しい場合、大規模で強力で高性能なアプリケーションを作成するために採用する必要がある C++ のメンタル モデルについて説明している文献はありますか? これには、コードのベスト プラクティスと、大規模なアプリケーションを確実に管理するというより抽象的な概念が含まれます。

4

2 に答える 2

2

(非ガベージ コレクション) C++ では、ガベージ コレクターではなく、デストラクタを介してメモリ管理が行われるという考え方があります。これの主な利点は次のとおりです。

(1) 非常に正確です。この言語は、いつ、どのような順序でデストラクタが呼び出され、メモリが解放されるかについて、非常に強力な保証を行います。

(2) シンプルで、透過的で、完全に移植可能で、オーバーヘッドが本当に最小限です。いつでも実行できる不透明なガベージ コレクション メカニズムに依存するのではなく、コンパイル対象のすべてのシステムでメモリ管理がどのように機能するかを正確に把握できます。また、いつ解放されるかわからない場合は、デバッグ出力をデストラクタに入れて確認できます。確かに最新のガベージ コレクターは非常に優れており、問題を引き起こすことはほとんどありません。

パフォーマンスに影響を与えるヒープメモリとスタックメモリには違いがあることは知っていますが、このトピックについて詳しくは知りません。

ヒープとスタックは、まさに C で使用されていたものです。スタックの基本的な考え方は、プログラムの関数呼び出しスタックを格納するメモリ ブロックです。スタックは、一連の「スタック フレーム」として配置されます。関数が呼び出されると、後で戻るときにどこに戻るべきかを示すポインターがプッシュされます。関数呼び出しのすべての引数は、スタックに連続して配置されます。このスタック フレームの大きさを示すカウンター/ポインターもあります。関数が呼び出されると、新しいスタック フレームが開始され、関数が戻ると、それがポップされます。関数のローカル変数もスタックに割り当てられます。スタックには固定サイズがあり、(通常は無限再帰によって) それを超えると、スタック オーバーフロー エラーでクラッシュします。スタックに入るすべてのものは、コンパイル時に既知の固定サイズを持っている必要があります。これは「sizeof」演算子によって決定されます。たとえば動的配列が必要な場合は、「ヒープ」を使用する必要があります。これにより、サイズが実行時にのみわかっている場合にメモリを割り当てることができ、任意の順序で破棄できます。

スタックの利点は、メモリ割り当てが基本的に瞬時に行われ (スタック フレーム ポインターが移動されるだけ)、解放が非常に快適な方法で行われることです。(関数から戻り、ローカル変数がスコープ外になると、単にスタックからポップしてメモリを解放します。) C++ は、スタックに割り当てられたオブジェクトとデストラクタについて非常に強力な保証を行います。関数が戻るとき、また通常の戻りの代わりに例外がスローされた場合でも、デストラクタは常に作成と逆の順序で呼び出されることが保証されます。基本的に、プログラムが異常終了しない限り ( std::terminate()、またはthrow 42)、または異常な事態が発生しない限り (デストラクタ自体が例外をスローする特殊なケースもありますが、基本的にこれを行うべきではありません。問題が発生するためです)、自動オブジェクトのデストラクタは非常に特定の時点で呼び出されます。時間、非常に特定の順序で。コンストラクタ/デストラクタを持つメンバー変数がある場合、それらも特定の順序で呼び出されます。継承が関係している場合...慣れていない場合は、それについて読むことができます。

基本的に、これはあらゆる種類のリソースを制御するために使用できる非常に強力で厳格なメカニズムです。メモリだけでなく、ソケット、プリンターへの接続、「クローズ」する必要がある C ライブラリ インスタンスへのポインターなど、あらゆる種類のリソースを制御できます。排他的な共有リソース、または不要になったときに特定の方法でクリーンアップする必要があるもの。

すべてがスタックに入るわけではありません。たとえば、動的にサイズ変更された配列がある場合、それはスタックに配置できず、C で一般的に教えられているのと同じ方法でヒープに配置する必要があります。スタックに移動します。)ただし、(C++ で演算子 new を使用して) ヒープに配置する場合は、delete の呼び出しとペアにする必要があります。そうしないと、メモリが解放されず、デストラクタが呼び出されません。

C++ では、これを管理するための推奨される方法は、スタックの機能を活用することです。自分で手動で delete を呼び出す代わりに、デストラクタが delete を呼び出すスタック割り当てオブジェクトを使用します。最も単純な例は "std::unique_ptr" (別名 boost::scoped_ptr) です。あなたが言及したshared_ptrもこのようなことをします。vector、list、map などのすべての C++ 標準コンテナーもこれを行います。

IMO これは基本的に C++ の福音です。適切に作成された C++ プログラムでは、すべてのメモリだけでなく、すべてのリソースがこの方法で管理され、デストラクタによって解放されます。これは非常に重要なRAIIのイディオムです: 「リソースの取得は初期化です」。ここで詳細を読むことができます: http://www.c2.com/cgi/wiki?ResourceAcquisitionIsInitialization

多くのプログラムでは、すべてのオブジェクトの所有者は 1 人だけであり、オブジェクト所有権図はツリーです。この場合、すべてがスタック割り当て/スタック割り当てのメンバー変数になり、すべてのメモリがオンになり、スタックによって管理されます。場合によっては、オブジェクトの所有権を共有する必要があります。所有権図は DAG (有向非巡回グラフ) です。次に、共有オブジェクトに shared_ptr を使用できます。それらはヒープに割り当てられますが、自動的に機能し、何もしなくてもメモリが管理されます。最も複雑なケースでは、所有権グラフに循環参照、循環があります。その場合に shared_ptr のみを使用すると、サイクルは解放されません。これは、ガベージ コレクターが「大変な仕事をしてくれる」と思われる場合です。最新の C++ では、依然としてほとんどの場所で shared_ptr を使用する方法が推奨されていますが、任意のサイクルの少なくとも 1 つのリンクでは代わりに weak_ptr を使用して、サイクルを「中断」し、自動的に解放できるようにします。これが必要になることはめったになく、必要な場合はそれほど手間がかかりません。これは、他の多くの言語の自動ガベージ コレクションに代わるエンジニアリング スタイルです。

于 2015-08-16T14:10:26.550 に答える