1. 概述
C++ STL库中,往vector末尾添加元素,一般使用push_back和emplace_back方法,那么他们有什么区别?
本文不会探讨STL源码的实现,而是从黑盒的角度,通过代码测试来分析区别。
2. 区别分析
2.1 先说说背景
在C++11之前,只支持push_back。 从C++11开始新增了emplace_back,同时也对push_back做了优化,底层会直接调用emplace_back。 所以,从C++11开始,两者可以随意使用,区别不大。
下面通过测试代码来分析:
2.2 C++98测试
测试代码如下:
#include <vector>
#include <iostream>
class Object {
int num;
public:
Object(int n) : num(n)
{
std::cout << "构造函数" << std::endl;
}
Object(const Object& p) : num(p.num)
{
std::cout << "拷贝构造函数" << std::endl;
}
};
int main()
{
std::vector<Object> objArray;
std::cout << "创建测试对象:" << std::endl;
Object obj = Object(1);
std::cout << std::endl;
std::cout << "push_back(obj):" << std::endl;
objArray.push_back(obj);
std::cout << std::endl;
std::cout << "push_back(1):" << std::endl;
objArray.push_back(1);
std::cout << std::endl;
std::cout << "xxxx_back(Object(1)):" << std::endl;
objArray.push_back(Object(1));
std::cout << std::endl;
}
输出结果:
创建测试对象:
构造函数
push_back(obj):
拷贝构造函数
push_back(1):
构造函数
拷贝构造函数
拷贝构造函数
xxxx_back(Object(1)):
构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
可以看到,往末尾添加元素时,会通过构造函数或者拷贝构造函数创建新的对象,有的场景甚至要构造好几次对象,然后才能添加到vector末尾。如果对象本身有几兆甚至几十兆,十分浪费性能。
下面看看在C++11上的情况。
2.2 c++11代码测试
这里要先提一下C++11新增的移动构造函数,简单理解就是创建对象时,直接把原来对象的数据拿过来,不用拷贝一份,原来的对象之后就不再使用了。 测试代码如下:
#include <vector>
#include <iostream>
class Object {
int num;
public:
Object(int n) : num(n)
{
std::cout << "构造函数" << std::endl;
}
Object(const Object& p) : num(p.num)
{
std::cout << "拷贝构造函数" << std::endl;
}
Object(const Object&& p) noexcept : num(p.num)
{
std::cout << "移动构造函数" << std::endl;
}
};
int main()
{
std::vector<Object> objArray;
std::cout << "创建测试对象:" << std::endl;
auto obj = Object(1);
std::cout << std::endl;
auto obj2 = Object(1);
std::cout << std::endl;
std::cout << "1. xxxx_back(obj):" << std::endl;
objArray.emplace_back(obj);
std::cout << std::endl;
objArray.push_back(obj2);
std::cout << std::endl;
std::cout << "2. xxxx_back(std::move(obj)):" << std::endl;
objArray.emplace_back(std::move(obj));
std::cout << std::endl;
objArray.push_back(std::move(obj2));
std::cout << std::endl;
std::cout << "3. xxxx_back(1):" << std::endl;
objArray.emplace_back(1);
std::cout << std::endl;
objArray.push_back(1);
std::cout << std::endl;
std::cout << "4. xxxx_back(Object(1)):" << std::endl;
objArray.emplace_back(Object(1));
std::cout << std::endl;
objArray.push_back(Object(1));
std::cout << std::endl;
}
输出结果:
创建测试对象:
构造函数
构造函数
1. xxxx_back(obj):
拷贝构造函数
拷贝构造函数
移动构造函数
2. xxxx_back(std::move(obj)):
移动构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数
3. xxxx_back(1):
构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数
构造函数
移动构造函数
4. xxxx_back(Object(1)):
构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数
移动构造函数
构造函数
移动构造函数
可以看到,不管是使用push_back,还是emplace_back,最多只会调用一次构造函数或者拷贝构造函数,其他都是调用的移动构造函数,而移动构造函数的性能消耗几乎可以忽略不计,所以两者区别不大。
3. 总结
综上所述:
C++11之前没的选,只能用push_back,如果一定要优化性能,那就尽量用指针吧。 C++11开始,可以随意使用,区别不大。 当然,对于大对象,最好实现移动构造函数,否则,还是会出现大量的数据拷贝。比如我把上面2.2节里的移动构造函数删除后执行,就会出现大量拷贝,结果如下:
创建测试对象:
构造函数
构造函数
1. xxxx_back(obj):
拷贝构造函数
拷贝构造函数
拷贝构造函数
2. xxxx_back(std::move(obj)):
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
3. xxxx_back(1):
构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
构造函数
拷贝构造函数
4. xxxx_back(Object(1)):
构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
构造函数
拷贝构造函数
编程之海 版权所有丨如未注明,均为原创丨转载请注明转自:https://codingsea.com/cvector-push_back%e5%92%8cemplace_back%e7%9a%84%e5%8c%ba%e5%88%ab/