C#からC ++への変換:メモリの手動管理

この記事では、C#で手動のメモリ管理を行い、それを使用して簡単な単方向リストを作成する方法を示します。



シードについては、少し先に実行して、最終的にそのようなリストのインスタンスがどのように作成されるかを示しましょう。
new CustomLinkedList< double , ListBasedAllocator<CustomLinkedListNode< double , int >>, int >()







安全でないコンテキストを使用しないことにすぐに同意します。 これにより、移植性が低下し、要求リストを作成するときに得られる喜びがすべてなくなります。



独自のポインター



幸いなことに(!)、. NETはC ++がサポートするため、ポインターをサポートしません。 マネージコードの開発時に利用できるのは



この問題は単純に解決します。この段階ではまったく解決しませんが、より良い時期まで延期します。 それまでの間、あらゆる場所でPointer型のパラメータを使用することに同意します。



コンテナに含まれる構造の変更



この記事では、固定サイズのデータ​​構造用のアロケーターを検討します。 このようなアロケーターのインターフェースについて説明します。





  1. /// <summary>
  2. ///特定のタイプのオブジェクトのアロケーター用の汎用インターフェース。
  3. /// </ summary>
  4. /// <typeparam name = "T">アロケーターが動作するオブジェクトのタイプ</ typeparam>
  5. /// <typeparam name = "TPointer">メモリへのアクセスに使用されるポインターのタイプ</ typeparam>
  6. パブリック インターフェイス ICustomTypeAllocator <T、TPointer>
  7. {
  8. /// <summary>
  9. /// 1つのオブジェクトのメモリ領域を選択し、この領域へのポインターを返します
  10. /// </ summary>
  11. TPointer Allocate();
  12. /// <summary>
  13. ///ポインタでメモリを解放します。
  14. /// checkErrorsフラグが設定されている場合、アロケーターは可能であればチェックします
  15. ///このポインタは実際に以前に割り当てられたメモリブロックを指していること、
  16. ///そして、そうでない場合、例外をスローします。
  17. /// </ summary>
  18. /// <param name = "pointer">解放するメモリ領域へのポインタ</ param>
  19. /// <param name = "checkErrors"> </ param>
  20. void Free(TPointerポインター、 bool checkErrors = false );
  21. /// <summary>
  22. ///アドレスポインターでオブジェクトへのアクセスを許可します。
  23. /// </ summary>
  24. /// <param name = "pointer">オブジェクトが配置されているメモリ領域のアドレス</ param>
  25. T this [TPointer pointer] { get ; セット ; }
  26. /// <summary>
  27. ///常に無効なポインターを返します
  28. /// </ summary>
  29. TPointer InvalidPointer { get ; }
  30. /// <summary>
  31. ///指定されたポインターが有効かどうかを確認します。
  32. /// </ summary>
  33. /// <param name = "pointer">チェックするポインター</ param>
  34. bool IsPointerValid(TPointerポインター);
  35. }
*このソースコードは、 ソースコードハイライターで強調表示されました。


このインターフェイス(またはそれ以前)を使用しようとすると、次の問題が発生します。

var pointer = allocator.Allocate();

allocator[pointer].Value = 0;






type-valueをtype Tとして指定した場合(つまり、これが主な目標です)、指定したコードはコンパイルされません。 理由は簡単です。アロケータ[ポインタ]操作の結果は、構造自体ではなく、指定されたアドレスに含まれる構造のコピーになります。 私は問題から2つの方法を知っています:

  1. アロケータ[ポインタ]の結果を一時変数に保存し、構造に対して必要な操作を実行して、書き戻します。
  2. 指定された方法で指定されたアドレスの値を変更する関数をアロケーターインターフェイスに追加します。
2番目のパスに沿って進みましょう。 これを行うには、参照によって1つのパラメーターを取り、voidを返すデリゲート型を宣言しpublic delegate void Modifier<T>( ref T value );



インターフェースに修飾子関数を追加します。

  1. /// <summary>
  2. ///修飾子関数を呼び出して、指定されたポインターアドレスのオブジェクトを変更します
  3. /// </ summary>
  4. /// <param name = "pointer">可変オブジェクトへのポインター</ param>
  5. /// <param name = "modifier">オブジェクトの値を変更する関数</ param>
  6. void Set(TPointerポインター、修飾子<T>修飾子);


デフォルトでは、ポインタを保存する場所はありません



空きノードのリストに基づいてアロケーターの実装を検討します。 C ++では、すべてが簡単です:ユーザーが管理するメモリに保存するユーザーのタイプに関係なく、ユーザー構造と、リスト内の次の空き要素へのポインターを正確に1つ持つ構造のユニオンとして要素を表すことができます。 安全でないコンテキスト外の.NETは、結合をサポートしません。 したがって、アロケーターのユーザーがポインターを保存する場所を明示的に提供する必要があります。 これを行うには、ポインターノードの唯一のメンバーでILinkedListNodeインターフェイスを宣言します{get; セット; }、アロケータが動作する要素の型はこのインターフェイスから継承される必要があります。

public class ListBasedAllocator<T>: ICustomTypeAllocator<T, int > where T: ILinkedListNode< int >





アロケーターとリストの実装



すべての自尊心のあるプログラマーは、空きノードのリスト、さらには一方向のリストに基づいてアロケーターを実装できるはずだと思います。 これはすべて、たとえばコーメンの本に記載されています。

完全な実装のソースコード(C#の場合)およびテストケース(F#の場合)は、 ここからダウンロードまたは表示できます

結論として



おそらく、リストとアロケーターを実装するためのコードの量は、それをF#に書き換えることによって型推論のために削減される可能性があります。



まだ本番環境でそのような魔法を使うべきではありません。 :)



PSまだあるでしょう!



All Articles