移动语义、标准集合和构造时间地址

Move semantics, standard collections, and construction time address

本文关键字:时间 地址 集合 语义 标准 移动      更新时间:2023-10-16

我当然想知道一些神奇的解决方案,但我对重组持开放态度。

因此,我有一个类DeviceDependent,具有以下构造函数

DeviceDependent(Device& device);

其存储对该设备的引用。设备可以更改状态,这将需要更改依赖于该设备的所有DeviceDependent实例。(你猜到了,这是我骑directX野兽的微不足道的尝试)

为了处理这个问题,我有函数DeviceDependent::createDeviceResources()DeviceDependent::onDeviceLost()

我计划将每个DeviceDependent实例注册到DeviceDependent构造函数中指定的设备。设备将保持如此注册的所有DeviceDependent实例的std::vector<DeviceDependent*>。然后,它将遍历该向量,并在适当的时候调用上面的函数。

这看起来很简单,但我特别喜欢的是,我可以在代码中的其他地方使用std::vector<DeviceDependent (or child)>,并快速迭代它们。例如,我有一个类Renderable,顾名思义,它表示一个可渲染对象,我需要至少每帧迭代一次,因此我不希望对象分散在整个内存中。

归根结底,问题是:

当我创建实体对象时,我依赖于移动语义。这纯粹是出于本能,我没有考虑复制像这样的大对象来将它们添加到std::vector<DeviceDependent (or child)>集合中。

然而,使用移动语义(我已经为那些不相信它的人测试了这一点),对象的地址会发生变化。更重要的是,在调用默认构造函数后,它会发生变化。这意味着我在调用device.registerDeviceDependent(this)DeviceDependant构造函数中的代码编译和运行良好,但设备会累积一个指针列表,一旦对象移动到向量中,这些指针就会失效。

我想知道是否有办法让我坚持这个计划并使它发挥作用。

我想到的事情:

  1. 使"real"矢量成为共享指针的集合,复制没有问题。对象大概不会更改地址。我不喜欢这个计划,因为我担心把东西放在堆里会损害迭代性能。

  2. 在对象被移动后调用register,这是我暂时要做的事情,但我不喜欢它,因为我觉得构造函数是做这件事的合适地方。那里不应存在不在某个设备清单上的DeviceDependent实例。

  3. 编写自己的移动构造函数或移动赋值函数。通过这种方式,我可以从设备中删除旧地址,并将其更改为新地址。我不想这样做,因为我不想随着类的发展不断更新它。

这与移动构造函数无关。问题是std::vector。当您向该向量添加新项时,它可能会重新分配内存,这将导致所有DeviceDependent对象都被转移到向量内部的新内存块。然后将构建每个项目的新版本,并删除旧版本。施工是复制施工还是移动施工无关;无论哪种方式,对象都会有效地更改其地址。

为了使代码正确,DeviceDependent对象需要在其析构函数中注销自己,并在复制和移动构造函数中注册自己。如果您没有删除那些构造函数,那么无论您对存储的其他决定是什么,都应该这样做。否则,如果调用这些构造函数,将执行错误的操作。

列表中没有的一种方法是通过调用reserve()来防止向量重新分配,该方法将存储最大数量的项。只有当您知道DeviceDependent对象数量的合理上限时,这才是可行的。然而,您可能会发现,保留一个估计,而不是完全消除向量重新定位,这使得取消注册和重新注册的成本变得微不足道。

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

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