push_back和emplace_back比较以及vector扩容
关于这部分内容,也是C++中的老生常谈的问题了,一些最基本的介绍这边也就不再进行了,着重对比一下两者的使用情况,然后再给出结论。
push_back和emplace_back的比较
使用测试类
class Person {
public:
// 无参构造
Person() {
}
// 有参构造
Person(int age) : _age(age) {
cout << "Construct a class: " << _age << endl;
}
// 拷贝构造
Person(const Person& p) : _age(p._age) {
cout << "Copy Construct a class: " << _age << endl;
}
// 转移构造
Person(const Person&& p) : _age(p._age) {
cout << "Move Construct a class: " << _age << endl;
}
// 析构
~Person() {
cout << "Destory a class: " << _age << endl;
}
private:
int _age;
};
测试过程
将实体类对象传入
如下代码所示,直接传入已经实体类对象,比较两者差异。
int main(int argc, char** argv) {
cout << "push_back demo: " << endl;
vector<Person> person1;
Person p1 = Person(10);
cout << "Now start push_back" << endl;
person1.push_back(p1);
cout << endl;
cout << "emplace_back demo: " << endl;
vector<Person> person2;
Person p2 = Person(20);
cout << "Now start emplace_back" << endl;
person2.emplace_back(p2);
system("pause");
return 0;
}
测试结果:
结论1:
- push_back:首先会调用构造函数创建出一个实体类对象p1,然后调用拷贝构造函数,将这个实体类对象传给push_back()这个函数中;
- emplace_back:首先会调用构造函数创建出一个实体类对象p2,然后调用拷贝构造函数,将这个实体类对象传给emplace_back()这个函数中;
- 如果直接传入实体类对象的话,先构造出这个对象,然后调用拷贝构造函数将这个对象传递给xxx_back()这个函数,最后插入到容器中;
将右值数字传入
int main(int argc, char** argv) {
cout << "push_back demo: " << endl;
vector<Person> person1;
cout << "Now start push_back" << endl;
person1.push_back(12);
cout << endl;
cout << "emplace_back demo: " << endl;
vector<Person> person2;
cout << "Now start emplace_back" << endl;
person2.emplace_back(22);
system("pause");
return 0;
}
测试结果:
结论2:
- push_back:插入一个右值的时候,在push_back()函数内部,会先构造出一个新的临时对象,然后调用移动构造函数将这个对象放入容器末尾,最后析构掉这个临时对象;
- emplace_back:插入一个右值的时候,在emplace_back()函数内部,会直接原地构造这个对象的同时直接插入,不会触发拷贝构造或者转移构造;
- 注意C++11引入右值引用、转移构造函数之后,push_back()调用的是移动构造函数,而在这之前,都是调用的拷贝构造函数;
将实体类对象move()转右值之后传入
int main(int argc, char** argv) {
cout << "push_back demo: " << endl;
vector<Person> person1;
Person p11 = Person(11);
cout << "Now start push_back" << endl;
person1.push_back(move(p11));
cout << endl;
cout << "emplace_back demo: " << endl;
vector<Person> person2;
Person p21 = Person(21);
cout << "Now start emplace_back" << endl;
person2.emplace_back(move(p21));
system("pause");
return 0;
}
测试结果:
可以看到与直接传入实体类的过程类似,只不过由于使用move()转成了右值,因此传参过程转移构造替代了拷贝构造过程;
vector扩容过程
测试代码如下:
int main(int argc, char** argv) {
cout << "push_back demo: " << endl;
vector<Person> person1;
cout << "vector size = " << person1.size() << endl;
Person p1 = Person(10);
Person p11 = Person(11);
cout << "Now start push_back" << endl;
person1.push_back(p1);
cout << "after insert p1, vector size = " << person1.size() << endl;
person1.push_back(p11);
cout << "after insert p11, vector size = " << person1.size() << endl;
cout << endl;
cout << "emplace_back demo: " << endl;
vector<Person> person2;
cout << "vector size = " << person2.size() << endl;
Person p2 = Person(20);
Person p21 = Person(21);
cout << "Now start emplace_back" << endl;
person2.emplace_back(p2);
cout << "after insert p2, vector size = " << person2.size() << endl;
person2.emplace_back(p21);
cout << "after insert p21, vector size = " << person2.size() << endl;
system("pause");
return 0;
}
测试结果:
由于是直接传入实体类,因此push_back()和emplace_back()实现效果基本一致,以push_back()为例;
- 创建vector容器,此时容器大小为0;
- 构造出2个对象p1和p11:2次construct;
- 向vector中传入p1,调用拷贝构造(copy construct)将实体对象传递给push_back()中的形参;此时容器内部没有其他元素,因此直接扩容(一般认为是2倍),使用allocate分配器分配新的内存空间,将元素插入到末尾即可,插入后空间大小为1;
- 向vector中传入p11,调用拷贝构造(copy construct)将实体对象传递给push_back()中的形参;此时空间不足,需要扩容,使用allocate分配器分配新的内存空间;
- 此时由于容器内部已经存在一个元素p1了,因此,将原容器中的对象拷贝构造(copy construct)到新容器中,并且调用析构函数(destruction)将原容器中对象删除;
- 最后将新插入的元素p11放在容器末尾即可;此时容器大小为2;
注意:只要引起了vector空间的重新配置,指向原vector的所有迭代器都已失效。
问题: vector为什么是以2倍方式进行扩容而不是其他扩容方式?
答:2倍方式扩容,一方面可以保证扩容的时间复杂度为常数级,另一方面也不会申请过多的堆内存空间防止浪费;