一 介绍
C++11开始,STL的一些容器中增加了emplace和emplace_back成员函数,例如 std::list 和 std::vector。
以 std::vector::emplace_back举例:
template< class... Args >
void emplace_back( Args&&... args ); (C++11 起) (C++17 前)
template< class... Args >
reference emplace_back( Args&&... args ); (C++17 起)
从C++17开始,改变了返回值,返回被插入元素的引用。
emplace_back 添加新元素到容器尾。元素通过 std::allocator_traits::construct 构造,它典型地用布置 new 于容器所提供的位置原位构造元素。参数 args... 以 std::forward<Args>(args)... 转发到构造函数。
二 emplace_back 和 push_back
简单来说,emplace_back是在容器所提供的位置直接构造,而push_back添加一个新元素到vector末尾,然后拷贝或者移动到新元素。
#include <iostream>
#include <vector>
class A {
public:
A() {
std::cout << "constructor" << std::endl;
}
A(int n) {
std::cout << "constructor int" << std::endl;
}
A(const A& a) {
std::cout << "copy constructor" << std::endl;
}
~A() {
std::cout << "destructor" << std::endl;
}
};
int main() {
A a;
{
std::vector<A> vc;
vc.reserve(10); // 排除扩容干扰
std::cout << std::endl;
std::cout << "push_back:" << std::endl;
vc.push_back(1);
vc.push_back(a);
}
{
std::vector<A> vc1;
vc1.reserve(10);
std::cout << std::endl;
std::cout << "emplace_back:" << std::endl;
vc1.emplace_back(1);
vc1.emplace_back(a);
}
std::cin.get();
return 0;
}
在push_back中先用参数1构造一个临时的A实例,然后拷贝到vector,最后析构临时的A实例;而emplace_back仅用参数1原位构造了一个A实例。对于插入a,表现一致,都是调用A的拷贝构造函数。
所以在支持emplace_back的场景下,推荐用emplace_back代替push_back。
三 补充(2020.10.16)
以 std::list push_back 为例,定义如下:
// std::list<T,Allocator>::push_back
void push_back( const T& value ); (1)
void push_back( T&& value ); (2)(since C++11)
可以看出,C++11开始,push_back 也增加了移动元素的形式,也就是说:
std::vector<A> vc;
// 等同, 均调用A的移动构造函数
{
A a;
vc.push_back(std::move(a));
}
{
A a;
vc.emplace_back(std::move(a));
}
为了进一步说明,将二中的例子进行了修改,如下:
#include <iostream>
#include <vector>
class A {
public:
A() { std::cout << "constructor" << std::endl; }
A(int n) { std::cout << "constructor int" << std::endl; }
A(const A& a) { std::cout << "copy constructor" << std::endl; }
A(A&& a) { std::cout << "move constructor" << std::endl; } // 增加移动构造函数
~A() { std::cout << "destructor" << std::endl; }
};
int main() {
{
std::vector<A> vc;
vc.reserve(10); // 排除扩容干扰
std::cout << std::endl;
std::cout << "push_back:" << std::endl;
vc.push_back(1);
{
std::cout << "------------------------" << std::endl;
A a;
vc.push_back(a);
}
{
std::cout << "------------------------" << std::endl;
A a;
vc.push_back(std::move(a));
}
}
{
std::vector<A> vc1;
vc1.reserve(10);
std::cout << std::endl;
std::cout << "emplace_back:" << std::endl;
vc1.emplace_back(1);
{
std::cout << "------------------------" << std::endl;
A a;
vc1.emplace_back(a);
}
{
std::cout << "------------------------" << std::endl;
A a;
vc1.emplace_back(std::move(a));
}
}
std::cin.get();
return 0;
}
结果如下:
push_back:
constructor int
move constructor // 与上例不同,原因见下
destructor
------------------------
constructor
copy constructor
destructor
------------------------
constructor
move constructor
destructor
destructor
destructor
destructor
emplace_back:
constructor int
------------------------
constructor
copy constructor
destructor
------------------------
constructor
move constructor
destructor
destructor
destructor
destructor
* 原因:在C++11中,拷贝构造/赋值 和 移动构造/赋值 必须同时提供或者同时不提供,才能保证类同时具有拷贝和移动语义,只声明一种的话,类仅能实现一种语义。参见 C++11 右值引用(2)移动构造和移动赋值函数