Moving semantics, standard collections, and build time addresses

Of course, I would like to know some magic fix, but I'm open to restructuring.

So, I have a DeviceDependent class with the following constructor

 DeviceDependent(Device& device); 

which stores the link to the device. A device can change state that requires a change in all instances of DeviceDependent that are dependent on that device. (You guessed that this is my insignificant attempt to ride on the beast directX)

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

I planned to register each instance of DeviceDependent on the device specified in the DeviceDependent constructor. The device will save std::vector<DeviceDependent*> all instances of DeviceDependent that will be registered. He then iterated over this vector and would call the above functions, if necessary.

It seemed simple enough, but I especially liked that I could have std::vector<DeviceDependent (or child)> somewhere else in the code and quickly iterate over them. For example, I have a Renderable class, which, since the proposed name is a rendered object, I need to iterate over it at least once, and because of this I did not want the objects to be scattered throughout memory.

For business purposes, here is the problem:

When I create solid objects, I relied on the semantics of movement. This was pure instinct, I did not consider the possibility of copying such large objects as them in order to add them to the std::vector<DeviceDependent (or child)> collection. (and still hates the idea)

However, with the semantics of movement (and I checked it for those who don't believe), the address of the object changes. What else will change after calling the default constructor. This means that my code inside the DeviceDependant constructor calls device.registerDeviceDependent(this) compiles and works fine, but the device accumulates a list of pointers that are invalid as soon as the object moves to the vector.

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

Things I was thinking about:

  • Creating a "real" vector is a collection of common pointers, without copying. The object does not seem to change the address. I do not like this plan because I am afraid that leaving things on the heap will harm the iterative work.

  • Calling a register after moving an object is what I do temporarily, but I don’t like it, because I think that the constructor is the right place for this. There should not be an instance of DeviceDependent that is not in any manifestation of the device.

  • Writing your own constructor for moving or moving destination functions. Thus, I could delete the old address from the device and change it to a new one. I do not want to do this because I do not want to update it as the class develops.

+4
source share
1 answer

This has nothing to do with move constructors. Problem: std :: vector. When you add a new element to this vector, it can redistribute its memory, and this will cause all DeviceDependant objects to be transferred to a new memory block, internal to the vector. Then new versions of each element will be built, and the old ones removed. Regardless of whether construction is copying or construction, it does not matter; objects effectively change their address anyway.

To make your code correct, DeviceDependant objects must unregister in their destructor and register themselves as copy-and move constructors. You should do this no matter what else you decide regarding storage, unless you delete these constructors. Otherwise, these constructors, if called, will do the wrong thing.

One approach not on your list would be to prevent the vector from being redistributed by calling reserve () with the maximum number of elements that you will keep. This is practical if you know a reasonable upper bound on the number of DeviceDependant objects. However, you may find that reservation reservation, not excluding the complete redistribution of vectors, makes it rare enough that the cost of not registering and re-registering becomes insignificant.

It seems your goal is to provide cache coherence for DeviceDependants. You may find that using std :: deque as the main repository avoids redistribution, while maintaining sufficient cache consistency. Or you can get cache coherence by writing a custom allocator or the new () operator.

On the sidelines, it looks like your design depends on performance that you can only guess about. If you actually measure it, you may find that using std :: vector> is okay, and the time it takes to repeat them is not significant. (Note that you do not need generic pointers here, since the vector is the sole owner, so you can avoid the overhead of link counting.)

+1
source

Source: https://habr.com/ru/post/1488810/


All Articles