C ++のGC:誘惑を克服する

最近、C ++でのシンプルで効率的な「手動」ガベージコレクションについてのメモが登場しました。 いくつかの複雑なクラス(ポインターを使用したアクティブな低レベルの作業が行われている)内のローカルガベージコレクションが正当化される可能性があることを完全に認めます。 しかし、大規模なプログラムの規模では、メモリリークを取り除くためのより信頼性の高い簡単な方法があります。 「すべての機会のための方法」であると主張せずに、少なくとも一部の読者にとって生活が楽になることを本当に願っています。



メソッドの本質は非常に単純です。各オブジェクトが特定のスコープの変数または別のオブジェクトの単純な(「スタック」)メンバーである場合、プログラムが未処理の例外からクラッシュしても、常に正しいクリーニングが行われます。 タスクは、さまざまな動的シナリオをこのスキームに減らすことです。



1.各オブジェクトには1人の所有者がいます。
最も重要な原則。 まず、プログラムの実行中にオブジェクトを削除できるのは、単一の所有者オブジェクトのみであり、他の誰も削除できないことを意味します。

最も単純な「静的」なケースでは、これは単に、通常の方法で所有者クラスにオブジェクトをメンバーとして含めることを意味します。 ポインターまたはリンクを介して所有者のクラスにオブジェクトを含めるためのよりエキゾチックなオプションとは対照的です(クラスではなく所有者クラスに注意してください)。

ルートプログラムオブジェクトは、main()でスタック変数として宣言されます。 さらに、main()の方がグローバル変数の形式よりも優れています。最初のケースではクリーニング順序を保証できるためです(翻訳単位によって分散されたグローバルオブジェクトのセットとは対照的です)。



この方法ですべてのオブジェクトを配置することにより、未処理の例外をスローした後でも、正しいストリッピングが実行されます。

class SomeParent { Child1 child1; Child2 child2; }; class Root { public: void Run(); private: SomeParent entry; }; int main(int argc, char **argv, char **envp) { Root().Run(); //   ,    }
      
      



もちろん、これは最も明らかなケースであり、ダイナミクスは関係しません。

実行中にオブジェクトを作成する必要がある場合はさらに興味深いです。



2.コンテナの所有。
動的に作成されたオブジェクトを保存するには、自動クリーニング機能付きのコンテナを使用します。 コンテナ自体は、クラスの通常の「スタック」メンバーとして宣言されます。 これは、ある種のスマートポインタオプション、または独自のコンテナ実装です。

 template <T> class One { public: One(); //  One(T *); //    void Clear(); //   T *operator->(); //   T *GetPtr(); ~One(); // }; //--- : class Owner { One<Entity> dynamicEntity; };
      
      



この場合、コンテナはオブジェクトの所有者であると言えます。



3.所有アレイ。
これらは、任意の基準で結合されたオブジェクトのコレクションを操作する必要がある場合に使用されます。 このような配列の特性は理解できます。デストラクタでは、すべての要素が正しく破壊されます。 ポインターによって配列に追加されると、オブジェクトは配列のプロパティになり、デストラクタでオブジェクトによって破棄されます。

 template <T> class Array { public: T & Add(); T & Add(const T &); // T & Add(T *); //   ~Array(); //   }; //   - : template <K,T> class ArrayMap { public: T & Add(const K &); T & Add(const K &, const T &); // T & Add(const K &, T *); //   ~ArrayMap(); //   }; //--- : class Owner { Array<String> stringsArray; ArrayMap<int,String> anotherStringsCollection; };
      
      



オブジェクトのコレクション全体の所有者が、所有者クラスの通常の「スタック」メンバーである配列であることは明らかです。

このような所有プリミティブから、かなり複雑なモデルを作成できます。 次のレベルの難易度は、所有者間のオブジェクトの転送です。



4.オブジェクトの転送は所有権を転送します。
各オブジェクトには1人の所有者がいます。 所有者はオブジェクトを別の所有者に転送できますが、オブジェクトへのアクセスは失われます。

内部ポインタの破壊的なコピーを配列とコンテナに追加することにより、所有権とともにオブジェクトを転送できます。

 template <T> class One { public: //... One(const One<T> &source); //ptr = source.ptr; source.ptr = NULL; void operator=(const One<T> &source); //ptr = source.ptr; source.ptr = NULL; bool IsEmpty(); //,     private: mutable T *ptr; }; //     
      
      





その結果、所有者が自分のメンバー関数から配列またはコンテナを返した場合、実際には子オブジェクトの所有権を呼び出し元オブジェクトに移しました。 呼び出し元が新しい所有者になりました。 また、オブジェクトは誰かによってクリーンアップされることが保証されているため、メモリリークになる可能性はありません。



オブジェクトの所有者が1人だけであるというルールを厳守する場合にのみ、これらすべてが機能することを再度思い出します。

これは、所有者がオブジェクトへのリンクまたはポインタを「out」に渡した場合でも、受信者はこのオブジェクトに何らかの機能を提供するように要求できることを意味します。 ただし、このオブジェクトは所有者ではないため、削除できません。

 class CleverEntity { public: void UpdateUI(Window *window) // ,     , //        { //window->... //: delete window     //     } }; class WindowWorker { public: void UpdateUI() { entity.UpdateUI(window.GetPtr()); } private: CleverEntity entity; One<Window> window; };
      
      







それだけです

「銀の弾丸」ではないかもしれませんが、ほとんどのアプリケーションにとっては十分です。

必要に応じて、より複雑なシナリオをコメントで作成できます。



PSこのトピックに興味がある人には、これらすべての概念が既に実装されているライブラリ、 U ++フレームワーク(BSDライセンス)のコアパッケージ( conceptexample array )に慣れることをお勧めします。 この手法を独自の方法で説明するとともに、その他の興味深い機能( 高速コンパイル高速破壊コピー配列の大きさの加速 )を説明します。



このアプローチのいくつかの理論的側面は、以前の記事の1つで説明されています。



All Articles