--------《深入應用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。