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 对象需要在其析构函数中注销自己,并在复制和移动构造函数中注册自己。如果您没有删除那些构造函数,那么无论您决定存储什么,都应该这样做。否则,如果调用这些构造函数,它们会做错事。

列表中没有的一种方法是通过调用具有您将存储的最大项目数的 reserve() 来防止向量重新分配。这只有在您知道 DeviceDependant 对象数量的合理上限时才实用。但是,您可能会发现保留一个估计值,虽然没有完全消除向量重新分配,但却非常罕见,以至于取消注册和重新注册的成本变得微不足道。

听起来您的目标是为 DeviceDependants 获得缓存一致性。您可能会发现使用 std::deque 作为主存储可以避免重新分配,同时仍然提供足够的缓存一致性。或者,您可以通过编写自定义分配器或运算符 new() 来获得缓存一致性。

顺便说一句,听起来您的设计是由您只是猜测的性能成本驱动的。如果您实际测量它,您可能会发现使用 std::vector> 很好,并且不会显着增加迭代它们所需的时间。(注意这里不需要共享指针,因为向量是唯一的所有者,所以可以避免引用计数的开销。)

于 2013-06-30T09:14:06.390 回答