官术网_书友最值得收藏!

1.10 stl容器新增的實用方法

下面講解stl容器新增的實用方法。

1.10.1 原位構造與容器的emplace系列函數

在介紹emplace和emplace_back方法之前,我們先看一段代碼:

以上代碼在一個循環里產生一個對象,然后將這個對象放入集合中,這樣的代碼在實際開發中太常見了。但是這樣的代碼存在嚴重的效率問題:循環中的t對象在每次循環時,都分別調用了一次構造函數、拷貝構造函數和析構函數,如下所示。

以上總共循環10次,調用30次。但實際上,我們的初衷是創建一個對象t,將其直接放入集合中,而不是將t作為一個中間臨時產生的對象,這樣的話,總共需要調用t的構造函數 10次就可以了。C++11提供了一個在這種情形下替代 push_back 的方法——emplace_back。通過使用emplace_back,可以將main函數中的代碼改寫如下:

經過以上改寫,在實際執行時只需調用Test類的構造函數10次,大大提高了執行效率。

同理,在這種情形下,對于像 std::list、std::vector 這樣的容器,其 push、push_front方法在C++11中也有對應的改進方法,即emplace/emplace_front方法。在C++Reference上將這里的emplace操作稱為“原位構造元素(EmplaceConstructible)”是非常貼切的。

除了使用emplace系列的函數原位構造元素,我們也可以為Test類添加移動構造函數(Move Constructor),復用產生的臨時對象t以提高效率。

1.10.2 std::map的try_emplace方法與insert_or_assign方法

因為std::map中元素的key是唯一的,所以在實際開發中經常會有這樣一類需求:向某個 map中插入元素時需要先檢測 map中指定的 key是否存在,不存在時做插入操作,存在時直接取來使用;或者在指定的key不存在時做插入操作,存在時做更新操作。

以PC版的QQ為例,好友列表中的每個好友都對應一個userid,當我們雙擊某個QQ好友頭像時,如果與該好友的聊天對話框(這里使用 ChatDialog 表示)已經存在,則直接將其激活并顯示,如果不存在,則將其創建并激活、顯示。假設我們使用std::map來管理這些聊天對話框,則在C++17之前的版本中,必須編寫額外的邏輯去判斷元素是否存在。可以將上述邏輯編寫如下:

在C++17中,map提供了一個try_emplace方法,該方法會檢測指定的key是否存在,如果存在,則什么也不做。函數簽名如下:

在以上函數簽名中,參數k表示需要插入的key;args參數是一個不定參數,表示構造value對象需要傳給構造函數的參數;通過hint參數可以指定插入的位置。

在前兩種簽名形式中,try_emplace 的返回值是一個 std::pair<T1,T2>類型,其中 T2是一個 bool 類型,表示元素是否成功插入 map 中;T1 是一個 map 的迭代器,如果插入成功,則返回指向插入位置的元素的迭代器,如果插入失敗,則返回 map 中已存在的相同key元素的迭代器。我們用 try_emplace改寫上面的代碼(這里不關心插入的位置,因此使用前兩個簽名):

使用 try_emplace 改寫后的代碼簡潔了許多。但是在以上代碼中需要注意:由于std::map<int64_t,ChatDialog*> m_ChatDialogs 的 value 是指針類型(ChatDialog*),而try_emplace的第 2個參數支持的是構造一個 ChatDialog對象,而不是指針類型,因此在某個 userid 不存在時,成功插入 map 后會導致相應的 value 為空指針。因此,我們利用inserted的值按需新建一個ChatDialog。當然,在新的C++規范(C++11及后續版本)提供了靈活而強大的智能指針以后,我們不應該再有任何理由去使用裸指針了,因此可以對以上代碼使用std::unique_ptr智能指針類型來重構:

以上代碼將 map 的類型從 std::map<int64_t,ChatDialog*>改為 std::map<int64_t,std::unique_ptr<ChatDialog>>,讓程序自動管理聊天對話框對象。程序在gcc/g++7.3中編譯并運行輸出如下:

在以上代碼中,構造函數和析構函數均被調用了3次,實際上,按最原始的邏輯(上文中普通版本)來講,ChatDialog 應該只被構造和析構 2 次,多出來的一次是因為在try_emplace時,無論某個userid是否存在于map中,均創建一個ChatDialog對象(這是額外的用不上的對象)。由于這個對象并沒有被用上,所以在出了 onDoubleClickFriendItem3函數的作用域后,智能指針對象 spChatDialog 被析構,進而導致這個額外的、用不上的ChatDialog對象被析構。這相當于做了一次無用功。為此,我們可以繼續優化代碼如下:

以上代碼按照之前裸指針版本的思路,按需創建了一個智能指針對象,避免了一次ChatDialog對象無用的構造和析構。再次編譯程序,執行結果如下:

在auto [iter,inserted]=m_ChatDialogs.try_emplace(userid,nullptr);語句中,m_ChatDialogs.try_emplace(userid,nullptr)函數返回兩個值,第2個值inserted是一個布爾變量,表示操作是否成功,如果成功,則在第1個返回值iter中含有函數調用成功后的數據。這種函數存在多個返回值且其中一個值表示函數是否調用成功,我們稱這種模式為ok-idiom模式,Golang開發者應該很熟悉這種ok-idiom模式。

為了方便驗證try_emplace函數支持原位構造(上文已經介紹),我們將map的value類型改成 ChatDialog 類型。在實際開發中,對于非 POD 類型的復雜數據類型,在 stl 容器中應該存儲其指針或者智能指針類型,而不是對象本身。修改后的代碼如下:

在以上代碼中,我們為 ChatDialog 類的構造函數增加了一個 userid 參數,因此當調用 try_emplace 方法時,需要傳遞一個參數,這樣 try_emplace 就會根據 map 中是否已存在同樣的 userid 按需構造 ChatDialog 對象了。程序的執行結果和上一個代碼示例應該是一樣的:

對于智能指針對象std::unique_ptr,在后面的小節中將詳細介紹。

上文介紹了map中指定的key不存在則插入相應的value,存在則直接使用該key對應的 value的情形。這里再來介紹 map中指定的 key不存在則插入相應的 value,存在則更新其value的情形。C++17為map容器新增了一個insert_or_assign方法,讓我們不再像C++17標準之前一樣額外編寫先判斷是否存在,不存在則插入,存在則更新的代碼了,這次我們可以一步到位。insert_or_assign的函數簽名如下:

其各個函數參數的含義與try_emplace一樣,這里不再贅述。

再來看一個例子:

在以上代碼中嘗試插入名為Tom的用戶,由于該人名在map中不存在,因此插入成功;當插入人名為Alex的用戶時,由于在map中已經存在該人名,因此只對其年齡進行更新,將Alex的年齡從45更新為27。程序執行結果如下:

本節介紹了 C++11/17 為 stl 容器新增的幾個實用方法,合理利用這些新增的方法會讓我們的程序變得更簡潔、高效。其實,新的C++標準一直在不斷改進和優化現有的 stl容器,如果經常需要與這些容器打交道,則建議留意C++新標準中這些容器的新動態。

主站蜘蛛池模板: 安仁县| 鸡西市| 建瓯市| 台前县| 泽库县| 四川省| 怀柔区| 塔河县| 颍上县| 钟祥市| 河西区| 中卫市| 延边| 五原县| 岱山县| 蓝山县| 东港市| 桐城市| 永吉县| 游戏| 河北区| 宣汉县| 鄄城县| 福清市| 浙江省| 内黄县| 灵台县| 察雅县| 宜黄县| 化隆| 浪卡子县| 峨眉山市| 仪征市| 于田县| 蓝田县| 安达市| 屯昌县| 阿拉善盟| 江城| 广饶县| 秦皇岛市|