C++ STL 容器vector添加元素函数emplace_back()和push_back()的使用差异
在对这两个函数进行比较之前,先提下vector这个容器的一个特点:
vector底层数据结构是一个动态数组,就是在实例化一个vector对象时,如果不手动对其容量进行设置的话,vector的默认容量为0,即capacity()返回值为0。随着向容器中添加元素,容器对象按照1 2 4 8 16 32 二倍扩容。GCC是按照二倍扩容,VS13是1.5倍扩容。
扩容后是一段新的内存空间,此时就需要将旧的内存空间里的所有元素拷贝到新的内存空间中去(如果元素为一个类的对象的话,此时会触发拷贝构造函数),然后再在新的内存空间中的原数据元素后面继续插入新的元素,且旧的内存空间会被释放,如果元素为一个类的对象的话,则会触发析构函数。
因此,在测试emplace_back()和push_back()函数时,需要对容器进行分配初始容量,避免多次多次插入触发扩容,影响测试效果。可以使用vector的成员函数reserve()初始化容器容量。
push_back()和emplace_back()函数的主要区别在于两个函数的底层实现机制不同。push_back()函数在向容器末尾添加新元素时,会先创建该元素,然后再将该元素移动或者拷贝到容器中;emplace_back()函数的底层实现是直接在容器尾部创建该新元素,不存在拷贝或者移动元素的过程。
为了更好的阐述两者的区别,来编写一个实现了构造函数、拷贝构造、移动构造的类,使用该类的对象作为元素插入容器,来查看具体区别,示例代码如下:
#include <iostream>
#include <vector>
// #include <unistd.h>
using namespace std;
class CPPTest{
public:
CPPTest(int num) : m_pNum(num) {
cout << "执行构造函数 ,m_pNum = " << m_pNum << " ! " << endl;
};
CPPTest(const CPPTest& obj) : m_pNum(obj.m_pNum) {
cout << "执行拷贝构造函数 ,m_pNum = " << m_pNum << " ! " << endl;
};
CPPTest(const CPPTest&& obj) : m_pNum(obj.m_pNum) {
cout << "执行移动构造函数 ,m_pNum = " << m_pNum << " ! " << endl;
};
~CPPTest() {
cout << "执行析构函数 ,m_pNum = " << m_pNum << " ! "<< endl;
}
private:
int m_pNum;
};
int main(int argc, char *argv[]) {
vector<CPPTest> pVec;
//初始化容器的容量,如果不对容器进行初始化,则容器的容量初始值为0,之后随着增加元素,容量为按照2倍的形式增长。即0、1、2、4...
//在此过程中,容器再重新分配空间时,会出发元素的拷贝函数,影响比对效果。
//在此,利用reserve函数初始化容器容量为5.
pVec.reserve(5);
cout << "emplace_back function execute!" << endl;
pVec.emplace_back(1);
cout << endl;
cout << "push_back function execute!" << endl;
pVec.push_back(2);
cout << endl;
// pause();
return 0;
}
编译后运行结果如下:
emplace_back function execute!
执行构造函数 ,m_pNum = 1 !
push_back function execute!
执行构造函数 ,m_pNum = 2 !
执行移动构造函数 ,m_pNum = 2 !
执行析构函数 ,m_pNum = 2 !
执行析构函数 ,m_pNum = 1 !
执行析构函数 ,m_pNum = 2 !
从运行结果输出来看,push_back() 底层实现时,优先选择调用移动构造函数,如果类中没有实现移动构造函数,则调用拷贝构造函数。
从以上分析可以得出结论,emplace_back函数和push_back函数在效率方面相比,emplace_back函数较优。