C++ 11 後,標準庫容器 std::vector
包含了成員函數 emplace
和 emplace_back
。emplace
在容器指定位置插入元素,emplace_back
在容器末尾添加元素。安全
emplace
和 emplace_back
原理相似,本文僅討論 push_back
和 emplace_back
。函數
首先看下 Microsoft Docs 對 push_back
和 emplace_back
的定義:字體
push_back
:Adds an element to the end of the vector.emplace_back
:Adds an element constructed in place to the end of the vector.二者的定義我用了加粗字體做區分,那麼如今問題就在於什麼叫作 constructed in place ?優化
再來看下官方文檔(www.cplusplus.com)怎麼介紹 emplace_back
的:ui
template <class... Args>
void emplace_back (Args&&... args);編碼Inserts a new element at the end of the vector, right after its current last element. This new element is constructed in place using
args
as the arguments for its constructor.
This effectively increases the container size by one, which causes an automatic reallocation of the allocated storage space if -and only if- the new vector size surpasses the current vector capacity.
The element is constructed in-place by callingallocator_traits::construct
withargs
forwarded.
A similar member function exists,push_back
, which either copies or moves an existing object into the container.spa
簡而言之,push_back
會構造一個臨時對象,這個臨時對象會被拷貝或者移入到容器中,然而 emplace_back
會直接根據傳入的參數在容器的適當位置進行構造而避免拷貝或者移動。翻譯
emplace_back
還須要 push_back
?這部份內容進一步對如何區分 push_back
和 emplace_back
作了解答。
Stack Overflow 有一項回答我認爲已經解釋的較爲清楚,所以這裏部分轉譯過來。指針
翻譯帶有我的理解,非直譯,原文參考:https://stackoverflow.com/questions/10890653/why-would-i-ever-use-push-back-instead-of-emplace-backcode
如下爲譯文:
關於這個問題我在過去 4 年思考良多,我敢說大多數關於 push_back
和 emplace_back
的解釋都不夠完善。
去年,我在一次關於 C++ 的介紹中(連接參考原文)討論了 push_back
和 emplace_back
的相關議題,這二者最主要的區別來自於:是使用隱式構造函數仍是顯示構造函數(implicit vs. explicit constructors)。
先看下面的示例:
std::vector<T> v; v.push_back(x); v.emplace_back(x);
傳統觀點認爲 push_back
會構造一個臨時對象,這個臨時對象會被移入到 v
中,然而 emplace_back
會直接根據傳入的參數在適當位置進行構造而避免拷貝或者移動。從標準庫代碼的實現角度來講這是對的,可是對於提供了優化的編譯器來說,上面示例中最後兩行表達式生成的代碼其實沒有區別。
真正的區別在於,emplace_back
更增強大,它能夠調用任何類型的(只要存在)構造函數。而 push_back
會更加嚴謹,它只調用隱式構造函數。隱式構造函數被認爲是安全的。若是可以經過對象 T
隱式構造對象 U
,就認爲 U
可以完整包含 T
的全部內容,這樣將 T
傳遞給 U
一般是安全的。正確使用隱式構造的例子是用 std::uint32_t
對象構造 std::uint64_t
對象,錯誤使用隱式構造的例子是用 double
構造 std::uint8_t
。
咱們必須在編碼時當心翼翼。咱們不想使用強大/高級的功能,由於它越是強大,就越有可能發生意想不到的錯誤。若是想要調用顯示構造函數,那麼就調用 emplace_back
。若是隻但願調用隱式構造函數,那麼請使用更加安全的 push_back
。
再看個示例:
std::vector<std::unique_ptr<T>> v; T a; v.emplace_back(std::addressof(a)); // compiles v.push_back(std::addressof(a)); // fails to compile
std::unique_ptr<T>
包含了顯示構造函數經過 T*
進行構造。由於 emplace_back
可以調用顯示構造函數,因此傳遞一個裸指針並不會產生編譯錯誤。然而,當 v
超出了做用域,std::unique_ptr<T>
的析構函數會嘗試 delete
類型 T*
的指針,而類型 T*
的指針並非經過 new
來分配的,由於它保存的是棧對象的地址,所以 delete
行爲是未定義的。
這不是爲了示例而特地寫的代碼,而是一個我遇到的實際問題。本來 v
是 std::vector<T *>
類型,遷移到 C++ 11 後,我修改成 std::vector<std::unique_ptr<T>>
。而且,那時我錯誤地認爲 emplace_back
可以作 push_back
所能作的全部事情,所以將 push_back
也改成了 emplace_back
。
若是我保留使用更加安全的 push_back
,那麼我會立馬發現這個 bug。不幸的是,我意外地隱藏了這個 bug 並直到幾個月後才從新發現它。