我正在测试一些代码,其中类中有一个std::vector
数据成员。该类既可复制又可移动,并且按照operator=
此处所述使用复制和交换习语实现。
如果有两个vector
s,说v1
大容量和v2
小容量,v2
复制到v1
( v1 = v2
),赋值后保留in中的大容量;v1
这是有道理的,因为下一次v1.push_back()
调用不必强制进行新的重新分配(换句话说:释放已经可用的内存,然后重新分配它以增加向量并没有多大意义)。
但是,如果对具有as 数据成员的类vector
进行相同的分配,则行为会有所不同,并且分配后不会保留更大的容量。
如果不使用 copy-and-swap 习语,而 copyoperator=
和 moveoperator=
是分开实现的,那么行为就如预期的那样(与普通的 non-member 一样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.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
这是没有复制和交换习语的输出(请注意在这种情况下如何x1.GetV().capacity() = 1000000
,正如预期的那样):
C:\TEMP\CppTests>cl /EHsc /W4 /nologo 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-op= and move-op=] x1.GetV().capacity() = 1000000 x2.GetV().capacity() = 1000 After x1 = x2: x1.GetV().capacity() = 1000000 x2.GetV().capacity() = 1000
可编译的示例代码如下(用VS2010 SP1/VC10测试):
#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();
}