你想知道的 std::vector::push_back 和 std::vector::emplace_back
引言
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
的:
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.
簡而言之,push_back
會構造一個臨時對象,這個臨時對象會被拷貝或者移入到容器中,然而 emplace_back
會直接根據傳入的參數在容器的適當位置進行構造而避免拷貝或者移動。
為什麼我們有了 emplace_back
還需要 push_back
?
這部分內容進一步對如何區分 push_back
和 emplace_back
做了解答。
Stack Overflow 有一項回答我認為已經解釋的較為清楚,因此這裡部分轉譯過來。
翻譯帶有個人理解,非直譯,原文參考://stackoverflow.com/questions/10890653/why-would-i-ever-use-push-back-instead-of-emplace-back
以下為譯文:
關於這個問題我在過去 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 並直到幾個月後才重新發現它。
引用
- //docs.microsoft.com/en-us/cpp/standard-library/vector-class?view=msvc-160
- //www.cplusplus.com/reference/vector/vector/emplace_back/
- //stackoverflow.com/questions/10890653/why-would-i-ever-use-push-back-instead-of-emplace-back