シードについては、少し先に実行して、最終的にそのようなリストのインスタンスがどのように作成されるかを示しましょう。
new CustomLinkedList< double , ListBasedAllocator<CustomLinkedListNode< double , int >>, int >()
安全でないコンテキストを使用しないことにすぐに同意します。 これにより、移植性が低下し、要求リストを作成するときに得られる喜びがすべてなくなります。
独自のポインター
幸いなことに(!)、. NETはC ++がサポートするため、ポインターをサポートしません。 マネージコードの開発時に利用できるのは
- 参照型インスタンス。 それらはすべてヒープに格納され、ガベージコレクターによって処理されます。 メモリ管理を作成するときに、それらについてすぐに忘れることができます。
- 値型のボックス化されたインスタンス。 原則として、それらは参照タイプのインスタンスとほとんど変わらず、同じ理由で私たちには適していません。
- マネージポインター。 ヒープとスタックの両方を指すことができます。 私の知る限り、それらはローカル変数に保存するか、呼び出されたときに他の関数に渡すことができます。 したがって、どちらも私たちに合わないでしょう。
この問題は単純に解決します。この段階ではまったく解決しませんが、より良い時期まで延期します。 それまでの間、あらゆる場所でPointer型のパラメータを使用することに同意します。
コンテナに含まれる構造の変更
この記事では、固定サイズのデータ構造用のアロケーターを検討します。 このようなアロケーターのインターフェースについて説明します。
*このソースコードは、 ソースコードハイライターで強調表示されました。
- /// <summary>
- ///特定のタイプのオブジェクトのアロケーター用の汎用インターフェース。
- /// </ summary>
- /// <typeparam name = "T">アロケーターが動作するオブジェクトのタイプ</ typeparam>
- /// <typeparam name = "TPointer">メモリへのアクセスに使用されるポインターのタイプ</ typeparam>
- パブリック インターフェイス ICustomTypeAllocator <T、TPointer>
- {
- /// <summary>
- /// 1つのオブジェクトのメモリ領域を選択し、この領域へのポインターを返します
- /// </ summary>
- TPointer Allocate();
- /// <summary>
- ///ポインタでメモリを解放します。
- /// checkErrorsフラグが設定されている場合、アロケーターは可能であればチェックします
- ///このポインタは実際に以前に割り当てられたメモリブロックを指していること、
- ///そして、そうでない場合、例外をスローします。
- /// </ summary>
- /// <param name = "pointer">解放するメモリ領域へのポインタ</ param>
- /// <param name = "checkErrors"> </ param>
- void Free(TPointerポインター、 bool checkErrors = false );
- /// <summary>
- ///アドレスポインターでオブジェクトへのアクセスを許可します。
- /// </ summary>
- /// <param name = "pointer">オブジェクトが配置されているメモリ領域のアドレス</ param>
- T this [TPointer pointer] { get ; セット ; }
- /// <summary>
- ///常に無効なポインターを返します
- /// </ summary>
- TPointer InvalidPointer { get ; }
- /// <summary>
- ///指定されたポインターが有効かどうかを確認します。
- /// </ summary>
- /// <param name = "pointer">チェックするポインター</ param>
- bool IsPointerValid(TPointerポインター);
- }
このインターフェイス(またはそれ以前)を使用しようとすると、次の問題が発生します。
var pointer = allocator.Allocate();
allocator[pointer].Value = 0;
type-valueをtype Tとして指定した場合(つまり、これが主な目標です)、指定したコードはコンパイルされません。 理由は簡単です。アロケータ[ポインタ]操作の結果は、構造自体ではなく、指定されたアドレスに含まれる構造のコピーになります。 私は問題から2つの方法を知っています:
- アロケータ[ポインタ]の結果を一時変数に保存し、構造に対して必要な操作を実行して、書き戻します。
- 指定された方法で指定されたアドレスの値を変更する関数をアロケーターインターフェイスに追加します。
public delegate void Modifier<T>( ref T value );
インターフェースに修飾子関数を追加します。
- /// <summary>
- ///修飾子関数を呼び出して、指定されたポインターアドレスのオブジェクトを変更します
- /// </ summary>
- /// <param name = "pointer">可変オブジェクトへのポインター</ param>
- /// <param name = "modifier">オブジェクトの値を変更する関数</ param>
- void Set(TPointerポインター、修飾子<T>修飾子);
デフォルトでは、ポインタを保存する場所はありません
空きノードのリストに基づいてアロケーターの実装を検討します。 C ++では、すべてが簡単です:ユーザーが管理するメモリに保存するユーザーのタイプに関係なく、ユーザー構造と、リスト内の次の空き要素へのポインターを正確に1つ持つ構造のユニオンとして要素を表すことができます。 安全でないコンテキスト外の.NETは、結合をサポートしません。 したがって、アロケーターのユーザーがポインターを保存する場所を明示的に提供する必要があります。 これを行うには、ポインターノードの唯一のメンバーでILinkedListNodeインターフェイスを宣言します{get; セット; }、アロケータが動作する要素の型はこのインターフェイスから継承される必要があります。
public class ListBasedAllocator<T>: ICustomTypeAllocator<T, int > where T: ILinkedListNode< int >
アロケーターとリストの実装
すべての自尊心のあるプログラマーは、空きノードのリスト、さらには一方向のリストに基づいてアロケーターを実装できるはずだと思います。 これはすべて、たとえばコーメンの本に記載されています。
完全な実装のソースコード(C#の場合)およびテストケース(F#の場合)は、 ここからダウンロードまたは表示できます 。
結論として
おそらく、リストとアロケーターを実装するためのコードの量は、それをF#に書き換えることによって型推論のために削減される可能性があります。
まだ本番環境でそのような魔法を使うべきではありません。 :)
PSまだあるでしょう!