我正在测试一些代码,其中一个类中有一个std::vector
数据成员。该类是可复制和可移动,operator=
使用复制和交换习惯用法<{3}}实现/强>
如果有两个vector
,请说v1
容量大,v2
容量小,v2
复制到v1
({{ 1}}),分配后保留v1 = v2
中的大容量;这是有道理的,因为下一个v1
调用不必强制新的重新分配(换句话说:释放已经可用的内存,然后重新分配它来增长向量没有多大意义)。
但是,如果对具有v1.push_back()
作为数据成员的类进行相同的赋值,则行为不同,并且在赋值后,更大的容量不< / em>保持。
如果复制和交换习惯用法不,并且复制vector
和移动operator=
分别实施 ,那么行为与预期一样(普通非会员operator=
)。
为什么?我们是否应该遵循复制和交换习惯,而是实施vector
( copy operator=(const X& other)
)和op=
( move {{ 1}})分别获得最佳性能?
这是具有复制和交换习惯用法的可重现性测试的输出(注意在这种情况下,operator=(X&& other)
之后,op=
是1,000,而不是1,000,000) :
x1 = x2
这是输出没有复制和交换习惯用法(注意本例x1.GetV().capacity()
如何符合预期):
C:\TEMP\CppTests>cl /EHsc /W4 /nologo /DTEST_COPY_AND_SWAP test.cpp test.cpp C:\TEMP\CppTests>test.exe v1.capacity() = 1000000 v2.capacity() = 1000 After copy v1 = v2: v1.capacity() = 1000000 v2.capacity() = 1000 [Copy-and-swap] x1.GetV().capacity() = 1000000 x2.GetV().capacity() = 1000 After x1 = x2: x1.GetV().capacity() = 1000 x2.GetV().capacity() = 1000
随后是可编译的示例代码(使用VS2010 SP1 / VC10进行测试):
x1.GetV().capacity() = 1000000
答案 0 :(得分:12)
使用std::vector
进行复制和交换确实会导致性能下降。这里的主要问题是复制std::vector
涉及两个不同的阶段:
复制和交换可以消除#2但不#1。考虑在swap()调用之前您将观察到的内容,但是在输入赋值操作之后。你有三个向量 - 即将要被覆盖的向量,一个是副本的向量,以及原始参数。
这清楚地暗示,如果即将被覆盖的载体具有足够或过剩的容量,则在创建中间载体时会浪费,并且源的额外容量会损失。其他容器也可以这样做。
复制和交换是一个很好的基准,特别是在涉及异常安全时,但它不是全局性能最高的解决方案。如果你处于一个狭窄的区域,那么其他更专业的实现可以更有效 - 但要注意,这个领域的异常安全是非常重要的,如果不进行复制和交换有时是不可能的。
答案 1 :(得分:5)
在X
案例中,您交换向量,而不是vector::operator=()
。作业保留了容量。 swap
交换容量。
答案 2 :(得分:2)
如果有两个向量,比如v1表示大容量,v2表示小 容量,v2复制到v1(v1 = v2),v1中的大容量是 任命后保留;这是有道理的,
这不适合我。
在赋值之后,我希望赋值的向量具有与赋值的向量相同的值和状态。我为什么要招致并且不得不拖延过剩的产能。
从标准的快速扫描中我不确定标准是否保证容量在较小的向量分配中保持不变。 (它会在vector::assign(...)
的调用中保留,因此可能是意图。)
如果我关心内存效率,在许多情况下我必须在分配后调用vector::shrink_to_fit()
,如果分配不适合我。
复制和交换具有缩小到适合的语义。实际上,它是通常的C ++ 98习惯用于缩小标准容器的配合。
因为下一次v1.push_back()调用不必强制重新分配 (换句话说:释放已有的内存,然后重新分配 它增长矢量没有多大意义。)
是的,但这取决于您的使用模式。如果您指定向量然后继续添加它们,则保留任何预先存在的容量是有意义的。如果在构建其内容后分配向量,则可能不希望保留多余的容量。
但是,如果对具有向量的类进行相同的赋值 作为数据成员,行为是不同的,并在分配后 更大的容量没有保留。
是的,如果你复制并交换该类。这样做也会复制并交换包含的向量,如上所述,这是一种实现缩小以适应的方法。
如果未使用复制和交换习惯用法,则复制operator =并移动 operator =单独实现,然后行为符合预期 (对于普通的非会员矢量)。
如上所述:这种行为是否符合预期值得商榷。
但如果它符合您的使用模式,即如果您希望在从另一个可能小于先前值的分配后继续增长向量,那么您确实可以通过使用不具有某些功能的东西获得一些效率丢弃现有的多余容量(例如vector::assign
)。
为什么?我们不应该遵循复制和交换习惯而是 实现operator =(const X&amp; other)(copy op =)和operator =(X&amp;&amp; 另外)(分别移动op =)以获得最佳性能?
如上所述,如果它符合您的使用模式,并且该分配和附加序列的性能至关重要,那么您确实可以考虑不使用交换和副本进行分配。交换和复制的主要目的是最小化实现(避免重复代码)和强大的异常安全性。
如果您选择不同的实施方案以获得最佳性能,那么您必须自己处理异常安全问题,并且需要为代码复杂性付出代价。