【问题标题】:Move semantics, standard collections, and construction time address移动语义、标准集合、构建时间地址
【发布时间】:2013-06-26 22:42:42
【问题描述】:

当然我想知道一些神奇的解决方法,但我愿意重组。

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

DeviceDependent(Device& device);

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

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

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

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

言归正传,问题来了:

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

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

我想知道是否有什么方法可以让我坚持这个计划并让它发挥作用。

我想到的事情:

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

  2. 在对象被移动后调用 register,这是我暂时要做的,但我不喜欢它,因为我觉得构造函数是执行此操作的合适位置。那里 不应存在​​不在某些设备清单上的 DeviceDependent 实例。

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

【问题讨论】:

  • DeviceDependent是一种设备吗?如果是这样,您应该只有Device,并且应该拥有多个Devices(取决于这个Device)。
  • "在调用默认构造函数后它会发生什么变化。" -- 这根本没有任何意义。一旦创建了一个对象,地址就是它的身份——它只能在你谈论不同的对象时改变。
  • 我不会将此作为答案发布,因为我不确定这是您要的,但here's one way to do what I think you're asking
  • @AnotherTest 不,DeviceDependent 不是一种设备。设备通过 Microsoft::WRL::ComPtr 管理多个 COM 接口(包装一个包装器 :p)。 DeviceDependent 使用这些接口,但不应该每个都有一个,如果它是派生类就会发生这种情况。想象一下 ToolChest,车库里的所有工人都依赖工具来运作,但他们本身并不是 ToolChest 的一种。 (至少我是这么想的)
  • @TomKnapen 我希望这不会花你太长时间。是的,我认为这会起作用,因为DeviceDependant::DeviceDependant(DeviceDependant && dep) : device_(nullptr) { dep.unregister_self(); std::swap(device_, dep.device_); register_self(); } 这是正确的移动构造函数,这意味着移动语义将起作用并且设备中的指针将更新。如果您认为这可能是最好的选择,我只是不想在更改 DeviceDependent 时继续更新该功能。最后一件事,我不必为派生类定义移动构造函数,对吧?

标签: c++ c++11 directx move standard-library


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 我认为您的意思是,无论我是否打算明确使用它们,我都不能也不应该畏缩编写所有构造函数。我在“猜测”性能问题,但这是有根据的猜测。我知道 DeviceDependnent 的一些孩子会很大。我知道会有成千上万的人。我知道我将不得不每秒多次迭代它们。我知道,作为一项规则,您不应该在对整个事物进行分析之前进行优化,但我认为您也不应该一头扎进低效率的境地。
猜你喜欢
  • 2020-02-23
  • 2013-05-17
  • 1970-01-01
  • 2013-02-11
  • 2012-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-01
相关资源
最近更新 更多