piecewise_construct存在的意義


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}); // 需要產生一個臨時變量
這是 emplaceemplace 和 insertinsert 最大的區別點。emplaceemplace 的語法看起來不可思議,在上 面的例子中后面兩個參數自動用來構造 vector 內部的 Foo 對象。做到這一點主要 使用了 C++11 的兩個新特性 變參模板變參模板 和 完美轉發完美轉發。”變參模板”使得 emplace 可以接受任意參數,這樣就可以適用於任意對象的構建。
”完美轉發”使得接收下來的參數 能夠原樣的傳遞給對象的構造函數,這帶來另一個方便性就是即使是構造函數聲明為 explicitexplicit 它還是可以正常工作,因為它不存在臨時變量和隱式轉換。

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 的特殊情況
mapmap 類型的 emplaceemplace 處理比較特殊,因為和其他的容器不同,map 的 emplace 函數把它接收到的所有的參數都轉發給 pairpair 的構造函數。對於一個 pairpair 來說,它既需要構造它的 keykey 又需要構造它的 valuevalue。如果我們按照普通的 的語法使用變參模板,我們無法區分哪些參數用來構造 keykey, 哪些用來構造 valuevalue。 比如下面的代碼:

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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM