关于c++中vector的push_back、拷贝构造copy constructor和移动构造move constructor
问题来自C++ Primer的第十三章练习题的13.48.是这样说的:
定义一个vector<String>并在其上多次调用push_back运行你的程序,并观察String被拷贝了多少次。
其中String是在习题中自己写的一个类似标准库string的类。(第一次看,自己写的String类确实有点挫,于是直接用了github上找到的CppPrimer答案中的String类。
//String.h
class String {
public:
String() : String("") {}
String(const char*);
String(const String&);
String& operator=(const String&);
String(String&&);
String& operator=(String&&);
~String();
const char* c_str() const { return elements; }
size_t size() const { return end - elements; }
size_t length() const { return end - elements - 1; }
private:
std::pair<char*, char*> alloc_n_copy(const char*, const char*);
void range_initializer(const char*, const char*);
void free();
private:
char* elements;
char* end;
std::allocator<char> alloc;
};
std::pair<char*, char*> String::alloc_n_copy(const char* b, const char* e)
{
auto str = alloc.allocate(e - b);
return{ str, std::uninitialized_copy(b, e, str) };
}
void String::range_initializer(const char* first, const char* last)
{
auto newstr = alloc_n_copy(first, last);
elements = newstr.first;
end = newstr.second;
}
String::String(const char* s)
{
char* sl = const_cast<char*>(s);
while (*sl) ++sl;
range_initializer(s, ++sl);
}
String::String(const String& rhs)
{
range_initializer(rhs.elements, rhs.end);
std::cout << "copy constructor" << std::endl;
}
String& String::operator=(const String& rhs)
{
auto newstr = alloc_n_copy(rhs.elements, rhs.end);
free();
elements = newstr.first;
end = newstr.second;
std::cout << "copy-assignment operator" << std::endl;
return *this;
}
String::String(String &&s) :elements(s.elements), end(s.end)
{
s.elements = s.end = nullptr;
cout << "move constructor" << endl;
}
String& String::operator=(String &&s)
{
if (this != &s)
{
free();
elements = s.elements;
end = s.end;
s.elements = s.end = nullptr;
}
cout << "move-assignment operator" << endl;
return *this;
}
void String::free()
{
if (elements) {
std::for_each(elements, end, [this](char& c) { alloc.destroy(&c); });
alloc.deallocate(elements, end - elements);
}
}
String::~String()
{
free();
}
String类中定义了拷贝构造和移动构造函数,并且在调用的时候打印提示信息。
一开始遇到的问题是(那时候书上还没有讲到移动构造函数,相当于把上面头文件中的移动构造给注释掉,只有拷贝构造),我随便写了测试:
void printInfo(vector<String> &v)
{
cout << "size:" << v.size() << " " << "capacity:" << v.capacity() << endl;
}
int main()
{
String s0("hello");
vector<String> vec;
printInfo(vec);
vec.push_back(s0);
printInfo(vec);
vec.push_back(s0);
printInfo(vec);
vec.push_back(s0);
printInfo(vec);
vec.push_back(s0);
printInfo(vec);
system("pause");
return 0;
}
结果是这样的:
由于push_back是调用的copy constructor,可以看到每一次push_back除了调用一次copy constructor外还要额外调用多次。当时我没想到是因为vector中的预留空间不足,需要额外分配空间(其实就是这个原因)。
而以上是没有移动构造的时候。下面加入了移动构造后,结果变成了:
这次更加直观了,可以看到,每次push_back在copy之前还要首先将已有的元素通过调用move constructor来移动,留出空间,然后再copy添加元素。之前之所以调用的全是copy constructor是因为没有定义move constructor,而此时编译器也不会自动合成(primer后面有说),这时候只能调用拷贝构造,效率就降低了。
那么可以预料,如果在测试之前首先调用vector的reserve方法预留出空间,那么就不需要一次次调用move constructor了。
结果如下:
这次在push_back之前加了一句
vec.reserve(4);
于是不再需要分配空间。
结论:vector添加元素在空间不够的时候需要重新分配,此时对于原来已有的元素会调用对象类的move constructor,而如果对象类没有定义移动构造,则会 使用copy constructor。所以给类添加移动构造函数可以在很多时候提高效率,因为没有它只能用拷贝构造来替代,而编译器又不会自动合成一个。
Cpp Primer是本好书,就是第一次啃有点慢= =。