We all knows that a std::vector will increase it's capacity dynamically. e.g. a vector has capacity=0 in the beginning, with pushing back elements, it's capacity increases gradually to 1, 2, 4, 8, ...
Each time it increases the capacity, it has to copy/move old elements into the newly created array of memory.
My question is, when the class of element has both copy constructor and rvalue constructor (for move-semantics), the std::vector will prefer the copy constructor instead of rvalue constructor. Why is that? Since there's obviously nobody need the old variables.
Here's an example:
#include <iostream>
#include <vector>
#include <chrono>
#include <cstring>
class foo {
public:
foo()
: id(-1)
, c(nullptr)
{
std::cout << "foo()\n";
}
foo(int i)
: id(i)
{
c = new char[100000000];
std::cout << "foo(" << i << ")\n";
}
// foo(const foo& rhs)
// {
// if (rhs.c)
// {
// id = rhs.id;
// std::cout << "copy " << id << std::endl;
// c = new char[100000000];
// std::memcpy(c, rhs.c, 100000000);
// }
// }
foo(foo&& rhs)
{
id = rhs.id;
std::cout << "move " << id << std::endl;
c = rhs.c;
rhs.c = nullptr;
}
~foo()
{
if (c)
{
delete [] c;
std::cout << "desctruct " << id << std::endl;
}
}
public:
char* c;
int id = -1;
};
std::vector<foo> getFoo()
{
std::vector<foo> vec;
// vec.reserve(3);
for (int i = 0; i < 3; ++i)
{
foo f(i);
vec.push_back(std::move(f));
std::cout << "capacity: " << vec.capacity() << std::endl;
}
return vec;
}
int main(int argc, char *argv[])
{
using namespace std::chrono;
auto tic = high_resolution_clock::now();
auto&& vec = getFoo();
std::cout << "vec size: " << vec.size() << std::endl;
auto toc = high_resolution_clock::now();
std::cout << duration_cast<microseconds>(toc - tic).count() << "us" << std::endl;
return 0;
}
As you can see, if I comment out the copy constructor, only move operation is involved:
foo(0)
move 0
capacity: 1
foo(1)
move 1
move 0
capacity: 2
foo(2)
move 2
move 1
move 0
capacity: 4
vec size: 3
165us
desctruct 2
desctruct 1
desctruct 0
However, if I uncomment the copy constructor, then there will be many copy operations, which is very expensive:
foo(0)
move 0
capacity: 1
foo(1)
move 1
copy 0
desctruct 0
capacity: 2
foo(2)
move 2
copy 1
copy 0
desctruct 1
desctruct 0
capacity: 4
vec size: 3
1008324us
desctruct 2
desctruct 1
desctruct 0
How can I avoid such copy operations?