emplace與insert的區別(C++11)
轉自時習之
C++11中大部分的容器對於添加元素除了傳統的 insert 或者 pusb_back/push_front 之外都提供一個新的函數叫做 emplace。 比如如果你想要向 std::vector 的末尾添加一個數據,你可以:
std::vector<int> nums;
nums.push_back(1);
你也可以使用:
std::vector<int> nums;
nums.empace_back(1);
避免不必要的臨時對象的產生
emplace 最大的作用是避免產生不必要的臨時變量,因為它可以完成 in place 的構造,舉個例子:
struct Foo { Foo(int n, double x); }; std::vector<Foo> v; v.emplace(someIterator, 42, 3.1416); // 沒有臨時變量產生 v.insert(someIterator, Foo(42, 3.1416)); // 需要產生一個臨時變量 v.insert(someIterator, {42, 3.1416}); // 需要產生一個臨時變量
這是 \(emplace\) 和 \(insert\) 最大的區別點。\(emplace\) 的語法看起來不可思議,在上 面的例子中後面兩個參數自動用來構造 vector 內部的 Foo 對象。做到這一點主要 使用了 C++11 的兩個新特性 \(變參模板\) 和 \(完美轉發\)。”變參模板”使得 emplace 可以接受任意參數,這樣就可以適用於任意對象的構建。
”完美轉發”使得接收下來的參數 能夠原樣的傳遞給對象的構造函數,這帶來另一個方便性就是即使是構造函數聲明為 \(explicit\) 它還是可以正常工作,因為它不存在臨時變量和隱式轉換。
struct Bar { Bar(int a) {} explicit Bar(int a, double b) {} }; int main(void) { vector<Bar> bv; bv.push_back(1); // 隱式轉換生成臨時變量 bv.push_back(Bar(1)); // 顯示構造臨時變量 bv.emplace_back(1); // 沒有臨時變量 //bv.push_back({1, 2.0}); // 無法進行隱式轉換 bv.push_back(Bar(1, 2.0)); // 顯示構造臨時變量 bv.emplace_back(1, 2.0); // 沒有臨時變量 return 0; }
map 的特殊情況
\(map\) 類型的 \(emplace\) 處理比較特殊,因為和其他的容器不同,map 的 emplace 函數把它接收到的所有的參數都轉發給 \(pair\) 的構造函數。對於一個 \(pair\) 來說,它既需要構造它的 \(key\) 又需要構造它的 \(value\)。如果我們按照普通的 的語法使用變參模板,我們無法區分哪些參數用來構造 \(key\), 哪些用來構造 \(value\)。 比如下面的代碼:
map<string, complex<double>> scp; scp.emplace("hello", 1, 2); // 無法區分哪個參數用來構造 key 哪些用來構造 value // string s("hello", 1), complex<double> cpx(2) ??? // string s("hello"), complex<double> cpx(1, 2) ???
所以我們需要一種方式既可以接受異構變長參數,又可以區分 key 和 value,解決 方式是使用 C++11 中提供的 tuple。
pair<string, complex<double>> scp(make_tuple("hello"), make_tuple(1, 2));
然後這種方式是有問題的,因為這裏有歧義,第一個 tuple 會被當成是 key,第二 個tuple會被當成 value。最終的結果是類型不匹配而導致對象創建失敗,為了解決 這個問題,C++11 設計了 piecewise_construct_t 這個類型用於解決這種歧義,它 是一個空類,存在的唯一目的就是解決這種歧義,全局變量 std::piecewise_construct 就是該類型的一個變量。所以最終的解決方式如下:
pair<string, complex<double>> scp(piecewise_construct,
make_tuple("hello"),
make_tuple(1, 2));
當然因為 map 的 emplace 把參數原樣轉發給 pair 的構造,所以你需要使用同樣 的語法來完成 emplace 的調用,當然你可以使用 forward_as_tuple 替代 make_tuple,該函數會幫你構造一個 tuple 並轉發給 pair 構造。
map<string, complex<double>> scp;
scp.emplace(piecewise_construct,
forward_as_tuple("hello"),
forward_as_tuple(1, 2));
所以對於 map 來說你雖然避免了臨時變量的構造,但是你卻需要構建兩個 tuple 。 這種 traedoff 是否值得需要代碼編寫者自己考慮,從方便性和代碼優雅性上來說:
scp.insert({"world", {1, 2}});
這種寫法都要勝過前面這個 emplace 版本。所以個人認為對於臨時變量構建代價不是 很大的對象(比如基礎類型)推薦使用 insert 而不是 emplace。
emplace與insert的區別(C++11)