C++ map(STL map)operator[]和insert()效率對比(深度剖析)

2020-07-16 10:05:21
通過前面的學習我們知道,map 容器模板類中提供有 operator[ ] 和 insert() 這 2 個成員方法,而值得一提的是,這 2 個方法具有相同的功能,它們既可以實現向 map 容器中新增新的鍵值對元素,也可以實現更新(修改)map 容器已儲存鍵值對的值。

舉個例子(程式一):
#include <iostream>
#include <map>  //map
#include <string> //string
using namespace std;
int main()
{
    std::map<string, string> mymap;
    //借用 operator[] 新增新鍵值對
    mymap["STL教學"] = "http://c.biancheng.net/java/";
    cout << "old mymap:" << mymap["STL教學"] << endl;
    //借用 operator[] 更新某個鍵對應的值
    mymap["STL教學"] = "http://c.biancheng.net/stl/";
    cout << "new mymap:" << mymap["STL教學"] << endl;

    //借用insert()新增新鍵值對
    std::pair<string, string> STL = { "Java教學","http://c.biancheng.net/python/" };
    std::pair<std::map<string, string>::iterator, bool> ret;
    ret = mymap.insert(STL);
    cout << "old ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
    //借用 insert() 更新鍵值對
    mymap.insert(STL).first->second = "http://c.biancheng.net/java/";
    cout << "new ret.iter = <" << ret.first->first << ", " << ret.first->second << ">" << endl;
    return 0;
}
程式執行結果為:

old mymap:http://c.biancheng.net/java/
new mymap:http://c.biancheng.net/stl/
old ret.iter = <{Java教學, http://c.biancheng.net/python/}, 1>
new ret.iter = <Java教學, http://c.biancheng.net/java/>

有關程式中 operator[ ] 和 insert() 成員方法的具體用法,讀者可翻閱前面的文章做詳細了解,這裡不再做過多解釋。

顯然,map 模板類中 operator[ ] 和 insert() 的功能發生了重疊,這就產生了一個問題,誰的執行效率更高呢?

總的來說,讀者可記住這樣一條結論:當實現“向 map 容器中新增新鍵值對元素”的操作時,insert() 成員方法的執行效率更高;而在實現“更新 map 容器指定鍵值對的值”的操作時,operator[ ] 的效率更高。

至於為什麼,有興趣的讀者可繼續往下閱讀。

向map容器中增添元素,insert()效率更高

首先解釋一下,為什麼實現向 map 容器中新增新鍵值對元素,insert() 方法的執行效率比 operator[ ] 更高?回顧程式一中,如下語句完成了向空 mymap 容器新增新的鍵值對元素:
mymap["STL教學"] = "http://c.biancheng.net/java/";
此行程式碼中,mymap["STL教學"] 實際上是 mymap.operator[ ](“STL教學”) 的縮寫(底層呼叫的 operator[ ] 方法),該方法會返回一個指向 “STL教學” 對應的 value 值的參照。

但需要注意的是,由於此時 mymap 容器是空的,並沒有 "STL教學" 對應的 value 值。這種情況下,operator[ ] 方法會預設構造一個 string 物件,並將其作為 "STL教學" 對應的 value 值,然後返回一個指向此 string 物件的參照。在此基礎上,程式碼還會將 "http://c.biancheng.net.java/" 賦值給這個 string 物件。

也就是說,上面這行程式碼的執行流程,可以等效為如下程式:
typedef map<string, string> mstr;
//建立要新增的預設鍵值對元素
pair<mstr::iterator, bool>res = mymap.insert(mstr::value_type("STL教學", string()));
//將新鍵值對的值賦值為指定的值
res.first->second = "http://c.biancheng.net/java/";

注意,這裡的 value_type(K,T) 指的是 map 容器中儲存元素的型別,其實際上就等同於 pair<K,T>。

可以看到,使用 operator[ ] 新增新鍵值對元素的流程是,先構造一個有預設值的鍵值對,然後再為其 value 賦值。

那麼,為什麼不直接構造一個要新增的鍵值對元素呢,比如:
mymap.insert(mstr::value_type("STL教學", "http://c.biancheng.net/java/"));
此行程式碼和上面程式的執行效果完全相同,但它省略了建立臨時 string 物件的過程以及解構該物件的過程,同時還省略了呼叫 string 類過載的賦值運算子。由於可見,同樣是完成向 map 容器新增新鍵值對,insert() 方法比 operator[ ] 的執行效率更高。

更新map容器中的鍵值對,operator[]效率更高

仍以程式一中的程式碼為例,如下分別是 operator[ ] 和 insert() 實現更新 mymap 容器中指定鍵對應的值的程式碼:
//operator[]
mymap["STL教學"] = "http://c.biancheng.net/stl/";
//insert()
std::pair<string, string> STL = { "Java教學","http://c.biancheng.net/python/" };
mymap.insert(STL).first->second = "http://c.biancheng.net/java/";
僅僅從語法形式本身來考慮,或許已經促使很多讀者選擇 operator[ ] 了。接下來,我們再從執行效率的角度對比以上 2 種實現方式。

從上面程式碼可以看到,insert() 方法在進行更新操作之前,需要有一個 pair 型別(也就是 map::value_type 型別)元素做引數。這意味著,該方法要多構造一個 pair 物件(附帶要構造 2 個 string 物件),並且事後還要解構此 pair 物件(附帶 2 個 string 物件的解構)。

而和 insert() 方法相比,operator[ ] 就不需要使用 pair 物件,自然不需要構造(並解構)任何 pair 物件或者 string 物件。因此,對於更新已經儲存在 map 容器中鍵值對的值,應優先使用 operator[ ] 方法。