emplace_back与push_back异同
vector的emplace_back与push_back
文章目录
- vector的emplace_back与push_back
-
- 前言
- 1.区别总览
- 2.push_back
-
- 支持右值引用
- 不支持传入多个构造参数
- 总是会进行拷贝构造
- 3.emplace_back
-
- emplace_back可以接受`多个构造参数`
- 支持原地构造
前言
在vector中,通过push_back
与emplace_back
都可以向尾部添加元素,用push_back也可以,用emplace_back也可以,只看最终造成的结果的话,似乎没有什么不同。我们总是能听到有人说emplace_back比push_back要快
,但是快在哪里呢? 什么情况下都快吗? 我们一起来跟着例子看一下。
1.区别总览
push_back | emplace_back | |
---|---|---|
是否支持右值引用 | 支持 | 支持 |
是否一定会发生拷贝构造 | 一定 | 不一定 |
是够支持直接传入多个构造参数 | 支持一个构造参数 | 支持多个构造参数 |
是够支持原地构造 | 不支持 | 支持 |
下面对这些特性
分别进行分析
2.push_back
支持右值引用
印象中,push_back是不支持右值引用
的,然而实际上,push_back一样支持右值引用,比如我们看push_back的源码
voidpush_back(value_type&& __x){ emplace_back(std::move(__x)); }template<typename... _Args>
又或者我们通过代码测试一下
std::string s1 = "hello";vector<std::string> strVec;strVec.push_back(std::move(s1));cout<<"s1="<<s1<<endl;cout << "strVec.size=" << strVec.size()<<endl;cout << "strVec[0]=" << strVec[0] << endl;
控制台输出
s1=
strVec.size=1
strVec[0]=hello
std::move将s1从一个左值
转化为一个右值引用,传入到push_back
作为参数,push_back
接收了该参数并调用了std::string
的移动拷造函数
,将资源转移到了容器内。这里提到了一个概念叫移动构造函数
,下面会专门介绍这个概念。
不支持传入多个构造参数
如果只需要传入一个构造参数就能构造对象 ,那么,可以直接在push_back
传入这个构造参数,完成对象的构造,例如
class testClass{public:int m_a;int m_b;testClass(int a){m_a = a;m_b = 10;}testClass(int a, int b ){m_a = a;m_b = b;}
};
void testContruct(){vector<testClass> list;list.push_back(1);cout<<"list.size="<<list.size()<<endl;cout << "list[0].m_a=" << list[0].m_a << " list[0].m_b=" << list[0].m_b << endl;
}
执行结果
list.size=1
list[0].m_a=1 list[0].m_b=10
在只需要一个参数就能构造的情况下,push_back
可以接受这个构造参数并调用相应构造方法进行构造。
如果构造时需要多个
构造参数,则只能手动构造
一个临时对象
,否则编译出错,例如
void testContruct(){//编译错误list.push_back(1,2);
}
需要这样写
void testContruct(){list.push_back(testClass(1,2));cout << "list.size=" << list.size() << endl;cout << "list[0].m_a=" << list[0].m_a << " list[0].m_b=" << list[0].m_b << endl;s
}
程序输出
list.size=1
list[0].m_a=1 list[0].m_b=2
这里与emplace_back是不同的,emplace_back可以接受多构造参数
的情况,下面会分析到。
总是会进行拷贝构造
这点怎么理解呢,我们通过一个例子来感受一下
首先创建一个类,实现其构造
,拷贝构造
以及析构函数
class BaseClassTwoPara {public:BaseClassTwoPara(int a){m_a = a;m_b = 999;cout << "construct " << m_a << " " << m_b << endl;}BaseClassTwoPara(int a, int b){m_a = a;m_b = b;cout << "construct " << m_a << " " << m_b << endl;}BaseClassTwoPara(const BaseClassTwoPara &rhs){m_a = rhs.m_a;m_b = rhs.m_b;cout << "copy construct " << m_a << " " << m_b << endl;}~BaseClassTwoPara(){cout << "delete " << m_a << " " << m_b << endl;}int m_a, m_b;
};
然后,创建一个vector,进行测试,分4种情况,
- 传入一个构造参数,让
push_back
自己构造对象
void test_TwoPara_push_back_cp()
{vector<BaseClassTwoPara> vl;cout << "test_TwoPara_push_back_cp"<<endl;vl.push_back(888);
}
控制台输出:
construct 888 999
copy construct 888 999
delete 888 999
test_TwoPara_push_back_cp
delete 888 999
能看到,emplace_back
首先调用BaseClassTwoPara
的构造函数,构造一个临时对象
,然后调用拷贝构造函数,将这个对象复制到容器中,然后立马析构掉临时对象,程序结束时,析构
掉容器内的对象。
所以,传入一个参数时,是会发生拷贝
的
- 传入已经构造好的对象
向vl中传入一个已经构造好的对象,观察输出
void test_TwoPara_push_back_cp()
{vector<BaseClassTwoPara> vl;BaseClassTwoPara b1(1, 2);vl.push_back(b1);cout << "test_TwoPara_push_back_cp" << endl;
}
控制台输出
construct 1 2
copy construct 1 2
test_TwoPara_push_back_cp
delete 1 2
delete 1 2
这里的流程是,b1首先调用两参数的构造函数,构造出一个局部对象
,然后将这个对象作为push_back参数传入,v1复制b1对象到容器内,但是不会向上面那个例子那样,把b1析构,因为b1不是临时对象,程序退出时,b1以及vl内的对象被析构。
所以,传入一个构造好的对象时,push_back会发生拷贝。
- 传入一个临时对象
通过BaseClassTwoPara(1,2)构造一个临时对象
传入push_back
void test_TwoPara_push_back_cp()
{vector<BaseClassTwoPara> vl;vl.push_back(BaseClassTwoPara(1,2));cout << "test_TwoPara_push_back_cp" << endl;
}
控制台输出:
construct 1 2
copy construct 1 2
delete 1 2
test_TwoPara_push_back_cp
delete 1 2
可以看到,临时对象通过构造函数被创建后,vl复制
该临时对象,然后析构临时对象,程序结束时,再析构容器内的对象,类似于上面提到的"传入一个构造参数,让push_back自己构造对象"这种情况。
所以,传入一个临时对象,push_back会发生拷贝。
- 传右值
传右值时,会调用BaseClassTwoPara的移动构造函数
,其实也是一种拷贝形式,这里会出现两种不同的情况
(1)当传入一个右值时,v1首先调用普通
构构造函数,构造一个BaseClassTwoPara临时对象,然后调用移动构造
函数,在容器内构造一个对象,然后析构临时对象,可以下面代码看出。
void test_TwoPara_push_back_cp()
{vector<BaseClassTwoPara> vl;vl.push_back(1);cout << "test_TwoPara_push_back_cp" << endl;
}
控制台输出
construct 1 999
move copy construct 1 999
delete 0 0
test_TwoPara_push_back_cp
delete 1 999
(2)当传入一个右值引用时,直接
调用移动构造函数,在容器中创建对象
void test_TwoPara_push_back_cp()
{vector<BaseClassTwoPara> vl;BaseClassTwoPara b1(1, 2);vl.push_back(std::move(b1))cout << "test_TwoPara_push_back_cp" << endl;
}
construct 1 2
move copy construct 1 2
test_TwoPara_push_back_cp
delete 0 0
delete 1 2
所以,不论什么情况,push_back至少发生一次
拷贝操作,这里说的拷贝也把移动构造算进来了,因为移动构造也是基于一个已有的对象,来创建一个新的对象。
3.emplace_back
emplace_back的大部分特性与push_back都是一样的,这一小节只分析不同点。
两点不同:
emplace_back可以接受多个构造参数
可以将多个构造参数传入emplace_back
,只要实现了对应的构造函数,就可以完成构造,例如
void test_TwoPara_push_back_cp()
{vector<BaseClassTwoPara> vl;vl.emplace_back(888,777);cout << "test_TwoPara_push_back_cp"<<endl;
}
控制台输出
construct 888 777
test_TwoPara_push_back_cp
delete 888 777
也就是emplace_back
这里直接调用了BaseClassTwoPara
两参数的构造函数,构造了一个BaseClassTwoPara对象,而push_back只能支持一个。
支持原地构造
从上面👆这个测试的输出可以看出来,如果直接输入构造参数,emplace_back的表现与push_back完全不同,emplace_back直接将对象构造在了容器内,而不是构造一个临时的再拷贝到容器内,这点如果利用好了,可以大大提高性能。
以上就是emplace_back与push_back的异同点。