你想知道的 std::vector::push_back 和 std::vector::emplace_back

引言

C++ 11 後,標準庫容器 std::vector 包含了成員函數 emplaceemplace_backemplace 在容器指定位置插入元素,emplace_back 在容器末尾添加元素。安全

emplaceemplace_back 原理相似,本文僅討論 push_backemplace_back函數

定義

首先看下 Microsoft Docs 對 push_backemplace_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 calling allocator_traits::construct with args 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_backemplace_back 作了解答。
Stack Overflow 有一項回答我認爲已經解釋的較爲清楚,所以這裏部分轉譯過來。指針

翻譯帶有我的理解,非直譯,原文參考:https://stackoverflow.com/questions/10890653/why-would-i-ever-use-push-back-instead-of-emplace-backcode

如下爲譯文:

關於這個問題我在過去 4 年思考良多,我敢說大多數關於 push_backemplace_back 的解釋都不夠完善。

去年,我在一次關於 C++ 的介紹中(連接參考原文)討論了 push_backemplace_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 行爲是未定義的。

這不是爲了示例而特地寫的代碼,而是一個我遇到的實際問題。本來 vstd::vector<T *> 類型,遷移到 C++ 11 後,我修改成 std::vector<std::unique_ptr<T>>。而且,那時我錯誤地認爲 emplace_back 可以作 push_back 所能作的全部事情,所以將 push_back 也改成了 emplace_back

若是我保留使用更加安全的 push_back,那麼我會立馬發現這個 bug。不幸的是,我意外地隱藏了這個 bug 並直到幾個月後才從新發現它。

引用

相關文章
相關標籤/搜索