目錄

  • 前言
  • 一、insert 與 emplace 的基本語法
  • 二、底層機制分析
  • 1. `insert` 的工作方式
  • 2. `emplace` 的工作方式
  • 三、性能差異:insert vs emplace
  • 四、隱式類型轉換時的區別
  • 五、是否可以用 emplace 完全取代 insert?
  • 1. 已經有完整對象的情況
  • 2. 構造函數參數存在歧義時
  • 3. 隱式類型轉換支持不同
  • 4. 語義與可讀性差異
  • 六、性能實際差距
  • 七、總結對比表
  • 八、實戰建議
  • 九、結語
  • 參考總結

前言

在使用 C++ STL 容器時,我們經常會遇到兩種插入元素的方式:insert()emplace()
它們看起來功能類似,但底層機制與性能差異卻很大,尤其是在 map / multimap / set 等關聯容器中更為明顯。

本文將深入分析這兩者的區別、使用場景及性能差異,並回答一個常見問題:

“既然 emplace 更高效,那它能完全取代 insert 嗎?”


一、insert 與 emplace 的基本語法

在 STL 容器中:

multimap<string, int> dict;

// 使用 insert 插入
dict.insert(std::make_pair("apple", 1));

// 使用 emplace 插入
dict.emplace("banana", 2);

兩種方式最終效果一致:都在容器中插入一個鍵值對。

但它們的底層實現機制不同:

  • insert():先構造一個對象,再將其拷貝或移動進容器;
  • emplace():直接在容器內部原地構造對象

二、底層機制分析

1. insert 的工作方式

當你調用:

dict.insert(std::make_pair("a", 1));

發生的事情是:

  1. 調用 std::make_pair("a", 1) 創建一個臨時對象;
  2. 將這個臨時對象拷貝或移動到容器中。

也就是説,insert 至少會涉及一次構造 + 一次拷貝/移動


2. emplace 的工作方式

當你調用:

dict.emplace("a", 1);

時,emplace 會在容器內部直接調用構造函數構造 pair<const string, int> 對象。
整個過程只有一次原地構造,沒有臨時對象。


三、性能差異:insert vs emplace

來看一個演示代碼:

#include <iostream>
#include <map>
#include <string>
using namespace std;

struct Item {
    string name;
    Item(string n) : name(n) {
        cout << "構造: " << name << endl;
    }
    Item(const Item& other) {
        cout << "拷貝構造: " << other.name << endl;
    }
};

int main() {
    multimap<int, Item> dict;

    cout << "--- insert ---" << endl;
    dict.insert(std::make_pair(1, Item("A")));

    cout << "--- emplace ---" << endl;
    dict.emplace(2, "B");
}

運行輸出:

--- insert ---
構造: A
拷貝構造: A
--- emplace ---
構造: B

可以看到:

  • insert 發生了構造 + 拷貝構造;
  • emplace 只有一次構造。

四、隱式類型轉換時的區別

有時我們會直接寫:

dict.insert({"apple", 1});

這行代碼雖然看起來簡潔,但仍然會:

  1. 先把 {"apple", 1} 轉換成 std::pair<const string, int>
  2. 然後拷貝或移動到容器中。

也就是説,即使使用隱式轉換,insert 仍然會產生臨時對象

而:

dict.emplace("banana", 2);

則會直接調用構造函數,不會構造臨時對象

所以:

即使 insert 使用了隱式類型轉換,它依然沒有 emplace 高效。


五、是否可以用 emplace 完全取代 insert?

這是最常見、也是最容易誤解的問題。

很多人認為:

“emplace 更高效,那以後都用 emplace 不就好了?”

但實際上,這樣做並不總是合適。原因如下👇


1. 已經有完整對象的情況

pair<string, int> p("apple", 1);
dict.insert(p);      // ✅ 合法,直接插入
dict.emplace(p);     // ❌ 錯誤!無法匹配構造函數

emplace 需要參數能構造出新對象,而不是接收一個現成對象。
此時使用 insert 更自然、語義更清晰。


2. 構造函數參數存在歧義時

struct A {
    A(int x, int y) {}
    A(pair<int, int> p) {}
};

multimap<int, A> m;
m.emplace(1, 1, 2);  // ❌ 編譯器可能歧義
m.insert({1, A(1, 2)}); // ✅ 明確無誤

insert 的顯式語義更強,不會引起構造函數選擇的歧義。


3. 隱式類型轉換支持不同

  • insert 支持隱式類型轉換;
  • emplace 不支持,要求參數嚴格匹配構造函數。

例如:

dict.insert({"a", 1}); // ✅ 自動轉為 pair<const string, int>
dict.emplace({"a", 1}); // ❌ 編譯錯誤

因此在一些初始化寫法中,insert 更寬容。


4. 語義與可讀性差異

insert 表示:

“我已經有一個完整對象,要放進容器中。”

emplace 表示:

“我需要直接在容器中構造一個新對象。”

這在代碼語義上也有明顯區別。
比如:

users.insert(user);      // 插入已存在對象
users.emplace(name, id); // 原地構造新對象

六、性能實際差距

在現代編譯器下(如 g++ -O2 或 MSVC 優化開啓時),
如果對象支持移動構造,那麼多出來的那次“移動”操作開銷非常小。

只有在以下場景下 emplace 才表現出顯著優勢:

  • 插入對象構造成本高(例如包含複雜成員或字符串);
  • 插入操作非常頻繁(如百萬次循環插入)。

七、總結對比表

特性

insert

emplace

原地構造

❌ 否

✅ 是

臨時對象

✅ 會產生

❌ 不產生

已有對象插入

✅ 合適

⚠️ 不便

支持隱式轉換

✅ 支持

❌ 不支持

構造歧義風險


⚠️ 可能有

性能

稍慢

✅ 稍快

語義

插入現成對象

原地構造對象

可讀性

✅ 明確

表達“構造並插入”的意圖


八、實戰建議

  1. 優先使用 emplace 當你直接傳遞構造參數時,用 emplace 能避免臨時對象。
dict.emplace("key", 100);
  1. 已有對象時使用 insert 例如從另一個容器中複製對象、或已有完整 pair 時:
pair<string, int> p("key", 100);
dict.insert(p);
  1. 模板或泛型代碼中慎用 emplace
    因為參數匹配嚴格,可能導致模板代碼編譯失敗。

九、結語

emplace 是 C++11 引入的一種“原地構造”機制,它在多數情況下比 insert 更高效,也更現代。
但這並不意味着 insert 已經“過時”或“應當廢棄”。

簡單來説:

  • “我已經有對象” → 用 insert
  • “我想直接構造對象” → 用 emplace

理解這兩者的語義差異,才能在實際項目中寫出既高效又清晰的代碼。


參考總結

  • insert:插入已有對象。
  • emplace:原地構造新對象。
  • 性能差距通常很小,但語義選擇更重要。