我在测试一些代码,在类中有一个std::vector数据成员。这个类是可复制的和可移动的,operator=是按照here描述的使用复制和交换成语实现的。
如果有两个vector,比如大容量的v1和容量小的v2,而v2被复制到v1 (v1 = v2),那么v1中的大容量将在分配之后保持;这是有道理的,因为下一个v1.push_back()调用不需要强制新的重新分配(换句话说:释放已经可用的内存,然后重新分配它来增长向量没有多大意义)。
但是,如果同一个赋值是以vector作为数据成员的类完成的,则行为是不同的,并且在分配之后不保留更大的容量。
如果没有使用复制和交换成语,并且分别实现了复制operator=和move operator=,那么行为就像预期的那样(对于普通的非会员vector)。
为什么会这样呢?我们不应该遵循复制和交换成语,而是分别实现operator=(const X& other) (复制op=)和operator=(X&& other) (移动op=)以获得最佳性能吗?
这是具有复制和交换习语的可重复测试的输出(在本例中,在x1 = x2之后,x1.GetV().capacity()是1,000而不是1,000,000):
C:\TEMP\CppTests>cl /EHsc /W4 /nologo /DTEST_COPY_AND_SWAP test.cpp test.cpp C:\TEMP\CppTests>test.exe v1 .容量()= 1000000 v2.容量()=1,000,v1= v2: v1.容量()= 1000000 v2.容量()=1,000拷贝-交换xGet1.V().capacity()= 1000000 x2.swap().capacity()=10,000在x1 = x2: x1.GetV().capacity() =10,000 xGet2.V().capacity()=1,000
这是没有复制和交换成语的输出(注意在本例中x1.GetV().capacity() = 1000000是如何实现的):
C:\TEMP\CppTests>cl /EHsc /W4 /nologo test.cpp test.cpp C:\TEMP\CppTests>test.exe v1 .容量()= 1000000 v2.容量()=1,000 v1= v2: v1.容量()= 1000000 v2.容量()=1,000拷贝-op=1,000拷贝-op().capacity()= 1000000 x2.GetV().capacity() =10,000 x1 = x2: x1.GetV().capacity() = 1000000 x2.V(Move).capacity()= 1000
可编译示例代码如下(用VS2010 SP1/VC10 10测试):
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
class X
{
public:
X()
{
}
explicit X(const size_t initialCapacity)
{
m_v.reserve(initialCapacity);
}
X(const X& other)
: m_v(other.m_v)
{
}
X(X&& other)
: m_v(move(other.m_v))
{
}
void SetV(const vector<double>& v)
{
m_v = v;
}
const vector<double>& GetV() const
{
return m_v;
}
#ifdef TEST_COPY_AND_SWAP
//
// Implement a unified op= with copy-and-swap idiom.
//
X& operator=(X other)
{
swap(*this, other);
return *this;
}
friend void swap(X& lhs, X& rhs)
{
using std::swap;
swap(lhs.m_v, rhs.m_v);
}
#else
//
// Implement copy op= and move op= separately.
//
X& operator=(const X& other)
{
if (this != &other)
{
m_v = other.m_v;
}
return *this;
}
X& operator=(X&& other)
{
if (this != &other)
{
m_v = move(other.m_v);
}
return *this;
}
#endif
private:
vector<double> m_v;
};
// Test vector assignment from a small vector to a vector with big capacity.
void Test1()
{
vector<double> v1;
v1.reserve(1000*1000);
vector<double> v2(1000);
cout << "v1.capacity() = " << v1.capacity() << '\n';
cout << "v2.capacity() = " << v2.capacity() << '\n';
v1 = v2;
cout << "\nAfter copy v1 = v2:\n";
cout << "v1.capacity() = " << v1.capacity() << '\n';
cout << "v2.capacity() = " << v2.capacity() << '\n';
}
// Similar to Test1, but now vector is a data member inside a class.
void Test2()
{
#ifdef TEST_COPY_AND_SWAP
cout << "[Copy-and-swap]\n\n";
#else
cout << "[Copy-op= and move-op=]\n\n";
#endif
X x1(1000*1000);
vector<double> v2(1000);
X x2;
x2.SetV(v2);
cout << "x1.GetV().capacity() = " << x1.GetV().capacity() << '\n';
cout << "x2.GetV().capacity() = " << x2.GetV().capacity() << '\n';
x1 = x2;
cout << "\nAfter x1 = x2:\n";
cout << "x1.GetV().capacity() = " << x1.GetV().capacity() << '\n';
cout << "x2.GetV().capacity() = " << x2.GetV().capacity() << '\n';
}
int main()
{
Test1();
cout << '\n';
Test2();
}发布于 2013-03-03 17:18:03
与std::vector的复制和交换确实会导致性能损失。这里的主要问题是复制std::vector涉及两个不同的阶段:
复制和交换可以消除#2,而不是#1。考虑一下在交换()调用之前,在输入赋值op之后会观察到什么。你有三个向量--一个即将被覆盖,一个是副本,另一个是原始论点。
这显然意味着,如果将要被覆盖的向量具有足够或过剩的容量,则在创建中间向量时会出现浪费,而源的额外容量也会丢失。其他容器也可以这样做。
复制和交换是一个很好的基准,特别是在异常安全方面,但它不是全球最高性能的解决方案。如果您处于一个紧要关头,那么其他更专业的实现可能会更高效--但请注意,这一领域的异常安全是不平凡的,如果不复制和交换,有时也是不可能的。
发布于 2013-03-03 17:19:18
在X情况下,您是在交换向量,而不是使用vector::operator=()。任务保持容量。swap交换能力。
发布于 2013-03-03 22:07:53
如果有两个向量,比如大容量的v1和小容量的v2,并且v2被复制到v1 (v1 = v2),那么v1中的大容量是在分配之后保持的;这是有道理的,
对我来说不是。
在赋值之后,我期望分配给向量的值和状态与从向量分配的值和状态相同。为什么我要招致和不得不拖着过剩的能力。
通过对标准的快速扫描,我不确定标准是否保证在从较小的向量分配的过程中,容量保持不变。(在调用vector::assign(...)时将保留它,因此这可能是目的。)
如果我关心内存效率,那么在很多情况下,如果分配不适合我的话,我必须在分配之后调用vector::shrink_to_fit()。
复制和交换具有收缩至适合的语义。实际上,这是标准容器收缩到合适的常用C++98成语.
因为下一个v1.push_back()调用不需要强制进行新的重新分配(换句话说:释放已经可用的内存,然后重新分配它来增长向量没有多大意义)。
是的,但这取决于您的使用模式。如果您分配向量,然后继续添加到它们,保持任何预先存在的能力是有意义的。如果在构建了向量内容之后分配了向量,则可能不希望将多余的容量分配给它。
但是,如果相同的赋值是以向量作为数据成员的类进行的,则行为是不同的,并且在分配之后不保留较大的容量。
如果您在该类中复制和交换,则为True。这样做也将复制和交换包含的向量,如上所述,这是实现收缩以适应的一种方法。
如果没有使用复制和交换成语,并且分别实现了复制operator=和move operator=,那么行为就像预期的那样(对于普通的非会员向量)。
正如上面所讨论的:这种行为是否与预期的行为是有争议的。
但是,如果它符合您的使用模式,即如果您希望在向量从可能小于先前值的另一个向量分配之后继续增长,那么您确实可以通过使用不丢弃现有过剩容量的东西来获得一些效率(例如,vector::assign)。
为什么会这样呢?我们不应该遵循复制和交换成语,而是分别实现operator=(const & other) (复制op=)和operator=( X&其他)(移动op=)以获得最佳性能吗?
如前所述,如果它符合您的使用模式,并且如果分配和附加序列的性能非常关键,那么您确实可以考虑不使用交换和复制进行分配。交换和复制的主要目的是最小的实现(避免重复代码)和强大的异常安全。
如果您选择不同的实现以获得最大的性能,那么您将不得不自己处理异常安全问题,并将在代码复杂性方面付出代价。
https://stackoverflow.com/questions/15188156
复制相似问题