--------《深入應用C++11:代碼優化與工程級應用》第2章使用C++11改進程序性能,本章將分別介紹右值引用相關的新特性。本節為大家介紹emplace_back減少內存拷貝和移動。---------
2.4 emplace_back減少內存拷貝和移動
emplace_back能就地通過參數構造對象,不需要拷貝或者移動內存,相比push_back能更好地避免內存的拷貝與移動,使容器插入元素的性能得到進一步提升。在大多數情況下應該優先使用emplace_back來代替push_back。所有的標准庫容器(array除外,因為它的長度不可改變,不能插入元素)都增加了類似的方法:emplace、emplace_hint、emplace_front、emplace_after和 emplace_back,關於它們的具體用法可以參考cppreference.com。這里僅列舉典型的示例。
vector的emplace_back的基本用法如下:

可以看出,emplace_back的用法比較簡單,直接通過構造函數的參數就可以構造對象,因此,也要求對象有對應的構造函數,如果沒有對應的構造函數,編譯器會報錯。如果把上面的構造函數注釋掉,在vs2013下編譯會報如下錯誤:
error C2661: “A::A”: 沒有重載函數接受 2 個參數
其他容器相應的emplace方法也是類似的。
相對push_back而言,emplace_back更具性能優勢。下面通過一個例子來看emplace_back和push_back的性能差異,如代碼清單2-5所示。
代碼清單2-5 emplace_back和push_back的比較
#include <vector>
#include <map>
#include <string>
#include <iostream>
using namespace std;
struct Complicated
{
int year;
double country;
std::string name;
Complicated(int a, double b, string c):year(a),country(b),name(c)
{
cout<<"is constucted"<<endl;
}
Complicated(const Complicated&other):year(other.year),country(other.country),name(std::move(other.name))
{
cout<<"is moved"<<endl;
}
};
int main()
{
std::map<int, Complicated> m;
int anInt = 4;
double aDouble = 5.0;
std::string aString = "C++";
cout<<"—insert--"<<endl;
m.insert(std::make_pair(4, Complicated(anInt, aDouble, aString)));
cout<<"—emplace--"<<endl;
// should be easier for the optimizer
m.emplace(4, Complicated(anInt, aDouble, aString));
cout<<"--emplace_back--"<<endl;
vector<Complicated> v;
v.emplace_back(anInt, aDouble, aString);
cout<<"--push_back--"<<endl;
v.push_back(Complicated(anInt, aDouble, aString));
}
輸出如下:
代碼清單2-5測試了map的emplace和vector的emplace_back,用map的insert方法插入元素時有兩次內存移動,而用emplace時只有一次內存移動;用vector的push_back插入元素時有兩次移動內存,而用emplace_back時沒有內存移動,是直接構造的。
可以看到,emplace/emplace_back的性能比之前的insert和push_back的性能要提高很多,我們應該盡量用emplace/emplace_back來代替原來的插入元素的接口以提高性能。需要注意的是,我們還不能完全用emplace_back來取代push_back等老接口,因為在某些場景下並不能直接使用emplace來進行就地構造,比如,當結構體中沒有提供相應的構造函數時就不能用emplace了,這時就只能用push_back。
