実際には非常に簡単です。直接参照を使用しないでください。IDなどの間接参照のレイヤーを使用します。例えば:
すべてのFoo「オブジェクト」(OOPの意味でのオブジェクトではなく、各Fooプロパティの配列のコレクション)を管理するFooManagerがあるとします。私が理解しているように、あなたが今していることはただインデックスを返すことです。Foo#42は、すべての配列のインデックス42にデータが配置されているFooであるとしましょう。次に、配列に穴を開けるFoo#42を削除します。他のすべての配列エントリを移動することはできますが、Foo#43は実際のインデックス43をポイントしなくなります。
したがって、IDテーブルを追加し、インデックスを返す代わりにIDを返します。新しいFooを作成するときは、そのデータを配列の最初の空きインデックスに追加します(たとえば42)。次に、新しい未使用のID(たとえば、1337)を生成します。これで、IDテーブルを更新して((ハッシュ)マップはこれに最適です)、ID 1337がインデックス42を指すようにすることができます。これで、ID1337を呼び出し元の関数に返すことができます。(呼び出し元の関数がデータが実際に格納されている場所を検出しないことに注意してください。これは無関係な情報です)
次回Fooを別のコードから更新する必要がある場合は、IDが使用されます。したがって、FooManagerのsetBar関数は、ID1337と新しいBar値を引数として呼び出されます。FooManagerはIDテーブルで1337を検索し、それがまだインデックス42にあることを確認し、Bar配列インデックス42を新しいBar値で更新します。
ここで、このシステムがその値を取得します。Foo1337を削除しましょう。FooManagerのremoveFooは、引数としてID1337で呼び出されます。1337を検索し、インデックス42にあります。ただし、その間に、10個の新しいFooが追加されました。したがって、現在できることは、インデックス42の値を52の値に置き換えるだけで、52から42に効果的に移動します。これは古いシステムで大きな問題を引き起こしますが、今はインデックステーブルを更新するだけで済みます。そこで、どのIDが52を指しているか(たとえば、1401)を調べて42に更新します。次に誰かがFooをID 1401で更新しようとすると、インデックステーブルで調べて、インデックス42にあることがわかります。
そのため、データを連続メモリに保持し、削除にかかるデータコピー操作の数は非常に少なく(Fooのプロパティごとに1つのコピー)、FooManagerの「外部」のコードは何かが起こったことに気付くことさえありません。それは死んだrefenceの問題さえ解決します。いくつかのコードがまだ削除された1337IDを持っていると仮定します(ぶら下がっている参照、これは悪いです!)、それがアクセス/変更しようとすると、FooManagerはそれを調べ、1337が存在しないことを確認し(もう)、きれいな警告を生成できます/ errorとcallstack。これにより、どのコードにまだぶら下がっている参照があるかを直接識別できます。
唯一の欠点は、IDテーブルでの余分なルックアップです。これで、ハッシュテーブルは非常に高速になりますが、Fooオブジェクトを変更するたびに追加の操作が必要になります。ただし、ほとんどの場合、マネージャーの外部からのアクセスは、マネージャーの内部へのアクセスよりもはるかに少なくなります。BulletManagerを使用すると、フレームごとに各弾丸が更新されますが、弾丸にアクセスして何かを変更/要求したり、弾丸を作成/削除するための呼び出しが発生する可能性は低くなります。ただし、これが逆の場合は、その状況に合わせて最適化するためにデータ構造を更新する必要があります。繰り返しになりますが、このような状況では、データがメモリ内のどこにあるかはそれほど重要ではないため、配列に「穴」があるか、まったく異なるデータレイアウトを使用することもできます。