[C++] push_back和emplace_back的区别

一、vector容器内存的特点

先介绍vector的内存特点,vector为了支持快速的随机访问,vector容器内元素以连续的方式存放,而为了提高在添加元素时的性能,vector允许在创建时额外预留一些多出来的储存空间,为添加新元素做准备。

vector的元素并未存在vector地址最开始处,而是在后续一段地址,即vector.data()所指的地址才开始存放元素。个人猜测从&vector到vector.data()这段内存中,应该存放了这个vector的一些信息,比如说长度、容量等。

cout << "创建一个空的容器person" << endl;
vector<Person> person;

cout << "扩容这个空的容器" << endl;
person.reserve(2);

cout << "容器的大小:" << person.size()<<endl;

cout << "容器的容量:" << person.capacity() << endl;

cout << "容器的地址:" << &person << endl;

cout << "容器的第一个元素的地址:" << person.data() << endl;

[C++] push_back和emplace_back的区别_第1张图片

在上述代码后,容器person的内存空间如下图所示:
[C++] push_back和emplace_back的区别_第2张图片

二、测试类

class Person {
public:
	// 无参构造
	Person() {

	}
	// 有参构造
	Person(int age) : _age(age) {
		cout << "调用有参构造,构造一个对象 "<< endl;
		cout << "构造的这个对象所在的地址:" << this << endl;
	}
	// 拷贝构造
	Person(const Person& p) : _age(p._age) {
		cout << "调用拷贝构造,构造一个对象 "<< endl;
		cout << "被拷贝的对象所在地址:" << &p << endl;
		cout << "构造的这个对象所在的地址:" << this << endl;
	}
	// 右值构造
	Person(const Person&& p) : _age(p._age) {
		cout << "调用右值构造,构造一个对象:" << _age << endl;
		cout << "所传进来的右值所指代的空间的地址:" << &p << endl;
		cout << "构造的这个对象所在的地址:" << this << endl;
	}
	// 析构
	~Person() {
		cout << "析构" << _age << endl;
	}
public:
	int _age;
};

有参构造函数传入的是对象的参数,用以赋予到创建的对象的属性上

拷贝构造函数传入的是已有对象的地址,用以拷贝

右值构造函数传入的是右值,指示一块没有名字的内存空间,同样用以拷贝

三、push_back()

1、push_back(实体对象)

首先使用测试类的有参构造函数,构造一个实体对象 p1 :

cout << "创建一个对象" << endl;
Person p1 = Person(10);

在这里插入图片描述

申请了一块内存空间存放实体对象p1,这时内存空间如下图:
[C++] push_back和emplace_back的区别_第3张图片

cout << "现在开始push_back" << endl;
person.push_back(p1);

cout << "容器的地址:" << &person<< endl;
cout << "容器的第一个元素的地址:" << &person[0] << endl;
cout << "这时容器内的第一个元素是:" << person[0]._age<< endl;
cout << endl;

[C++] push_back和emplace_back的区别_第4张图片

从输出中可以看出,push_back(实体对象)时,会把实体对象的地址&p1传入测试类中,调用测试类的拷贝构造函数(该构造函数的输入是一个地址值),在容器内存中元素所在储存的末尾,构造一个新的对象。通过传入的实体对象的地址&p1,将这个实体对象内的数据拷贝进新创建的对象中,完成添加操作。
[C++] push_back和emplace_back的区别_第5张图片

这时并没有调用析构函数,也就是说原有的实体对象p1仍存在。
[C++] push_back和emplace_back的区别_第6张图片

2、push_back(参数)

重新弄一个新的容器:
[C++] push_back和emplace_back的区别_第7张图片

[C++] push_back和emplace_back的区别_第8张图片
如果在push_back()函数内直接传入一个参数:

cout << "现在开始push_back参数10" << endl;
person.push_back(10);

cout << "容器的地址:" << &person << endl;
cout << "容器的第一个元素的地址:" << &person[0] << endl;
cout << "这时容器内的第一个元素是:" << person[0]._age << endl;
cout << endl;

[C++] push_back和emplace_back的区别_第9张图片
从输出可以看出,push_back(参数)时,会首先调用测试类的有参构造函数,创建一个临时对象,然后再将这个临时对象所在的内存空间的地址作为右值传入测试类的右值构造函数中,测试类的右值构造会在容器内存中元素所在储存空间的末尾,创建一个新的对象,并通过地址,到临时对象所在的内存空间中,将里面的数据拷贝进新的对象里,完成添加操作。

最后,临时对象的析构函数会被调用,这个对象将被析构,不再存在。

[C++] push_back和emplace_back的区别_第10张图片
[C++] push_back和emplace_back的区别_第11张图片

[C++] push_back和emplace_back的区别_第12张图片
[C++] push_back和emplace_back的区别_第13张图片

3、push_back( move(实体对象) )

[C++] push_back和emplace_back的区别_第14张图片
[C++] push_back和emplace_back的区别_第15张图片
首先,创造一个实体对象

Person p1 = Person(10);

在这里插入图片描述
[C++] push_back和emplace_back的区别_第16张图片
将实体对象 p1 经过move() 函数转成右值之后,传入push_back()函数中

cout << "现在开始push_back右值move(p1)" << endl;
person.push_back(move(p1));

cout << "容器的地址:" << &person << endl;
cout << "容器的第一个元素的地址:" << &person[0] << endl;
cout << "这时容器内的第一个元素是:" << person[0]._age << endl;
cout << endl;

[C++] push_back和emplace_back的区别_第17张图片
从输出可以看出,move()将实体对象所在内存空间的地址转化成了右值,因此在push_back时便会调用测试类的右值构造函数,在容器的末尾创建一个新的对象,并去到右值所指示的内存空间中,将里面的数据拷贝到新对象上。

与同样会调用测试类的右值构造函数的push_back(参数)的区别在于,push_back( move(实体对象) )在完成数据的拷贝后,不会调用析构函数析构右值所指示的那部分内存空间,也就是说,实体对象 p1 仍然存在,且指针 p1 所指示的内存空间内的值没有变化,仍然可以被访问。

cout << "实体对象p1仍存在,地址:" << &p1 << endl;
cout << "p1所指处的地址内仍有数据:" << p1._age << endl;

在这里插入图片描述
[C++] push_back和emplace_back的区别_第18张图片
[C++] push_back和emplace_back的区别_第19张图片
[C++] push_back和emplace_back的区别_第20张图片

四、emplac_back()

1、emplace_back(实体对象)

cout << "创建一个空的容器person" << endl;
vector<Person> person;

cout << "扩容这个空的容器" << endl;
person.reserve(2);

cout << "容器的大小:" << person.size()<<endl;

cout << "容器的容量:" << person.capacity() << endl;

cout << "容器的地址:" << &person << endl;

cout << "容器的第一个元素的地址:" << person.data() << endl << endl << endl;

cout << "创建一个对象" << endl;

Person p1 = Person(10);

cout << endl << endl << endl;

cout << "现在开始emplace_back实体对象p1" << endl;
person.emplace_back(p1);

cout << endl << endl << endl;

cout << "容器的地址:" << &person << endl;
cout << "容器的第一个元素的地址:" << &person[0] << endl;
cout << "这时容器内的第一个元素是:" << person[0]._age << endl;
cout << endl;

[C++] push_back和emplace_back的区别_第21张图片
从输出中可以看出,emplace_back(实体对象),会调用测试类的拷贝构造函数,在容器末尾创建一个新的对象,并把实体对象p1的地址作为参数传入拷贝构造函数中,拷贝构造函数到达地址所指示的内存空间,将里面的数据拷贝到新创建的对象内,完成添加操作。

操作完成后,同样没有析构函数被调用,也就是说原有实体对象 p1 仍然存在。

[C++] push_back和emplace_back的区别_第22张图片

2、emplace_back(参数)

cout << "创建一个空的容器person" << endl;
vector<Person> person;

cout << "扩容这个空的容器" << endl;
person.reserve(2);

cout << "容器的大小:" << person.size()<<endl;

cout << "容器的容量:" << person.capacity() << endl;

cout << "容器的地址:" << &person << endl;

cout << "容器的第一个元素的地址:" << person.data() << endl << endl << endl;
	
cout << endl << endl << endl;

cout << "现在开始emplace_back参数" << endl;
person.emplace_back(10);

cout << endl << endl << endl;

cout << "容器的地址:" << &person << endl;
cout << "容器的第一个元素的地址:" << &person[0] << endl;
cout << "这时容器内的第一个元素是:" << person[0]._age << endl;
cout << endl;

[C++] push_back和emplace_back的区别_第23张图片
当使用emplace_back(10)的时候,从输出中就可以明显看出与push_back(10)的差别了。

emplace_back(10)并没有创建临时对象,最后析构临时对象的操作。而是调用测试类的有参构造函数,在容器的末尾直接创建一个新的对象,这时就完成添加操作了。

[C++] push_back和emplace_back的区别_第24张图片
由于没有创建临时对象、拷贝、析构的操作,也就是没有通过临时对象中转,性能自然比push_back(10)要好。

3、emplace_back( move(实体对象) )

cout << "创建一个空的容器person" << endl;
vector<Person> person;

cout << "扩容这个空的容器" << endl;
person.reserve(2);

cout << "容器的大小:" << person.size()<<endl;

cout << "容器的容量:" << person.capacity() << endl;

cout << "容器的地址:" << &person << endl;

cout << "容器的第一个元素的地址:" << person.data() << endl << endl << endl;

cout << "创建一个对象" << endl;

Person p1 = Person(10);

cout << endl << endl << endl;

cout << "现在开始emplace_back( move(实体对象) )" << endl;
person.emplace_back(move(p1));

cout << endl << endl << endl;

cout << "容器的地址:" << &person << endl;
cout << "容器的第一个元素的地址:" << &person[0] << endl;
cout << "这时容器内的第一个元素是:" << person[0]._age << endl;
cout << endl;

cout << "实体对象p1仍存在,地址:" << &p1 << endl;
cout << "p1所指处的地址内仍有数据:" << p1._age << endl;

[C++] push_back和emplace_back的区别_第25张图片
与push_back( move(实体对象) )没有区别,就不细说了。

五、总结

push_back和emplace_back的区别只在传入的参数是对象的构造参数时,如push_back(10),emplace_back(10)。

两种情况下:
push_back(10)会先调用类的有参构造函数,创建一个临时对象,再在容器的末尾调用类的右值构造函数(因为这个临时对象没有名字,属于右值,所以只能使用右值构造函数),输入临时对象的地址,创建一个新的对象,根据地址值到临时对象所在的内存空间中拷贝数据,最后再调用临时对象的析构函数,析构这个临时对象。

emplace_back(10)少了临时对象这一步,直接在容器的末尾调用类的有参构造函数创建一个新的对象,完成添加操作。

你可能感兴趣的:(C++,c++)