【发布时间】:2014-09-26 01:28:35
【问题描述】:
我最近意识到,在 C++11(或者至少是我的实现,Visual C++)中添加移动语义已经积极(并且相当戏剧性地)破坏了我的优化之一。
考虑以下代码:
#include <vector>
int main()
{
typedef std::vector<std::vector<int> > LookupTable;
LookupTable values(100); // make a new table
values[0].push_back(1); // populate some entries
// Now clear the table but keep its buffers allocated for later use
values = LookupTable(values.size());
return values[0].capacity();
}
我遵循这种模式来执行容器回收:我会重复使用同一个容器而不是销毁和重新创建它,以避免不必要的堆释放和(立即)重新分配。
在 C++03 上,这工作得很好——这意味着这段代码曾经返回 1,因为向量是按元素复制,而它们的底层缓冲区保持原样。因此,我可以修改每个内部向量,知道它可以使用与以前相同的缓冲区。
然而,在 C++11 上,我注意到这会导致右侧 move 到左侧,这会对每个元素执行逐元素移动分配左侧的向量。这反过来又导致向量丢弃其旧缓冲区,突然将其容量减少到零。因此,由于过多的堆分配/释放,我的应用程序现在速度大大降低。
我的问题是:这种行为是错误还是故意的?它甚至是由标准指定的吗?
更新:
我刚刚意识到这种特定行为的正确性可能取决于a = A() 是否可以使指向a 元素的迭代器无效。但是,我不知道移动分配的迭代器失效规则是什么,所以如果您知道它们,可能值得在您的答案中提及这些规则。
【问题讨论】:
-
在复制或移动中
capacity会发生什么尚未明确。 -
你为什么不做
for (auto& v : values) { v.clear(); }?无论如何,这似乎是意图。 -
@Mehrdad:我没有看到缓冲区是如何被重用的。在这两种情况下,
values中的元素都被完全重构了。我看到的唯一区别是默认向量容量的选择(C++11 要求为 0,而 C++03 没有要求)。我很惊讶 C++03 中的代码更快。 -
移动分配可以移动分配+移动构造单个元素或整个容器(取决于分配器)。因此,它可以使所有迭代器无效。不过,我在标准中找不到合适的报价。
-
也许我应该限定我的陈述:就操作而言,移动分配必须是 O(N),因为必须销毁 LHS 的现有元素。但尚不清楚是否保证仅在可能的情况下移动指针(即元素分配的 O(x))。
标签: c++ performance c++11 memory-management move-semantics