1. 程式人生 > >emplace與insert的區別(C++11)

emplace與insert的區別(C++11)

ise 對象 insert war 不可 調用 很大的 scp 添加

轉自時習之
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)