4

Of course I would like to know some magic fix to this but I am open to restructuring.

So I have a class DeviceDependent, with the following constructor

DeviceDependent(Device& device);

which stores a reference to the device. The device can change state, which will necessitate a change in all DeviceDependent instances dependent on that device. (You guessed it this is my paltry attempt to ride the directX beast)

To handle this I have the functions DeviceDependent::createDeviceResources(), DeviceDependent::onDeviceLost().

I planned to register each DeviceDependentinstance to the device specified in the DeviceDependent constructor. The Device would keep a std::vector<DeviceDependent*> of all DeviceDependent instances so registered. It would then iterate through that vector and called the above functions when appropriate.

This seemed simple enough, but what I especially liked about it was that I could have a std::vector<DeviceDependent (or child)> somewhere else in the code and iterate over them quickly. For instance I have a class Renderable which as the name suggest represents a renderable object, I need to iterate over this once a frame at least and because of this I did not want the objects to be scattered throughout memory.

Down to business, here is the problem:

When I create the solid objects I relied on move semantics. This was purely by instinct I did not consider copying large objects like these to add them to the std::vector<DeviceDependent (or child)> collection. (and still abhor the idea)

However, with move semantics (and I have tested this for those who don't believe it) the address of the object changes. What's more it changes after the default constructor is called. That means my code inside the constructor of DeviceDependant calling device.registerDeviceDependent(this) compiles and runs fine, but the device accumulates a list of pointers which are invalidated as soon as the object is moved into the vector.

I want to know if there is someway I can stick to this plan and make it work.

Things I thought of:

  1. Making the 'real' vector a collection of shared pointers, no issue copying. The object presumably will not change address. I don't like this plan because I am afraid that leaving things out on the heap will harm iteration performance.

  2. Calling register after the object has been moved, it's what I'm doing provisionally but I don't like it because I feel the constructor is the proper place to do this. There should not exist an instance of DeviceDependent that is not on some device's manifest.

  3. Writing my own move constructor or move assignment functions. This way I could remove the old address from the device and change it to the new one. I don't want to do this because I don't want to keep updating it as the class evolves.

4

1 に答える 1

1

これは、ムーブ コンストラクターとは関係ありません。問題は std::vector です。そのベクターに新しい項目を追加すると、そのメモリが再割り当てされる可能性があり、その結果、すべての DeviceDependant オブジェクトがベクター内部の新しいメモリ ブロックに転送されます。次に、各アイテムの新しいバージョンが構築され、古いバージョンが削除されます。構築がコピー構築かムーブ構築かは関係ありません。オブジェクトはどちらの方法でも効果的にアドレスを変更します。

コードを正しくするには、DeviceDependant オブジェクトをデストラクタで登録解除し、コピー コンストラクタとムーブ コンストラクタの両方で登録する必要があります。これらのコンストラクターを削除していない場合は、ストレージについて他に何を決定するかに関係なく、これを行う必要があります。そうしないと、それらのコンストラクターが呼び出された場合、間違った動作をします。

リストにないアプローチの 1 つは、格納するアイテムの最大数を指定して reserve() を呼び出して、ベクトルの再割り当てを防ぐことです。これは、DeviceDependant オブジェクトの数の妥当な上限がわかっている場合にのみ実用的です。ただし、ベクトルの再割り当てを完全に排除するわけではありませんが、見積もりを予約すると、登録解除と再登録のコストが取るに足らないほど少なくなることがわかる場合があります。

あなたの目標は、DeviceDependants のキャッシュ コヒーレンシを取得することのようです。メイン ストレージとして std::deque を使用すると、十分なキャッシュ コヒーレンシを提供しながら、再割り当てを回避できる場合があります。または、カスタム アロケータまたは演算子 new() を記述して、キャッシュの一貫性を得ることができます。

余談ですが、あなたの設計は、あなたが推測しているだけのパフォーマンス コストによって動かされているように思えます。実際に測定すると、 std::vector> を使用しても問題なく、それらを反復するのにかかる時間はそれほど大きくないことがわかります。(ベクトルが唯一の所有者であるため、ここでは共有ポインターは必要ないことに注意してください。参照カウントのオーバーヘッドを回避できます。)

于 2013-06-30T09:14:06.390 に答える