Я тестировал некоторый код, в котором есть элемент данных std::vector
внутри класса. Класс является одновременно подвижным и подвижным, а operator=
реализуется, как описано здесь, используя идиома и .
Если есть два vector
s, скажем v1
с большой емкостью и v2
с небольшой емкостью, а v2
скопирован на v1
(v1 = v2
), большая емкость в v1
сохраняется после назначения; это имеет смысл, так как в следующих вызовах v1.push_back()
нет необходимости форсировать новые перераспределения (другими словами: освобождение уже доступной памяти, а затем перераспределение ее для увеличения вектора не имеет большого смысла).
Но, если одно и то же назначение выполняется с классом, имеющим vector
в качестве элемента данных, поведение отличается, а после назначения большая емкость не сохраняется.
Если идиома копирования и свопинга не используется, а copy operator=
и move operator=
реализованы отдельно, тогда поведение будет таким же, как ожидалось (как для обычных нечленов vector
s).
Почему? Должны ли мы не следовать идиоме "копирование и своп" и вместо этого использовать operator=(const X& other)
(copy op=
) и operator=(X&& other)
(move op=
) отдельно для оптимальной производительности?
Это результат воспроизводимого теста с идиомой копирования и смены (обратите внимание, как в этом случае после x1 = x2
, x1.GetV().capacity()
равно 1000, а не 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();
}