深度剖析 C++ 對象池自動回收技術(shù)實(shí)現(xiàn)
對象池可以顯著提高性能,如果一個(gè)對象的創(chuàng)建非常耗時(shí)或非常昂貴,頻繁去創(chuàng)建的話會非常低效。對象池通過對象復(fù)用的方式來避免重復(fù)創(chuàng)建對象,它會事 先創(chuàng)建一定數(shù)量的對象放到池中,當(dāng)用戶需要創(chuàng)建對象的時(shí)候,直接從對象池中獲取即可,用完對象之后再放回到對象池中,以便復(fù)用。這種方式避免了重復(fù)創(chuàng)建耗 時(shí)或耗資源的大對象,大幅提高了程序性能。本文將探討對象池的技術(shù)特性以及源碼實(shí)現(xiàn)。
對象池類圖
-
ObjectPool:管理對象實(shí)例的pool。
-
Client:使用者。
適用性:
-
類的實(shí)例可重用。
-
類的實(shí)例化過程開銷較大。
-
類的實(shí)例化的頻率較高。
效果:
-
節(jié)省了創(chuàng)建類實(shí)例的開銷。
-
節(jié)省了創(chuàng)建類實(shí)例的時(shí)間。
-
存儲空間隨著對象的增多而增大。
問題
目前縱觀主流語言的實(shí)現(xiàn)方式無外乎3個(gè)步驟:
-
初始創(chuàng)建一定數(shù)量的對象池(也允許從外面添加對象)。
-
從對象池中取對象來使用。
-
用完之后返回對象池。
一般情況下這樣是OK的,可能存在的問題是在第三步,有兩個(gè)問題:
-
不方便,每次都需要顯式回收對象。
-
忘記將對象放回對象池,造成資源浪費(fèi)。
改進(jìn)動機(jī)
解決顯式回收的問題,實(shí)現(xiàn)自動回收,省心省力。改進(jìn)之后的對象池?zé)o須提供release方法,對象會自動回收,改進(jìn)之后的類圖如下。
技術(shù)內(nèi)幕
借助c++11智能指針,因?yàn)橹悄苤羔樋梢宰远x刪除器,在智能指針釋放的時(shí)候會調(diào)用刪除器,在刪除器中我們將用完的對象重新放回對象池。思路比較簡單,但實(shí)現(xiàn)的時(shí)候需要考慮兩個(gè)問題:
-
什么時(shí)候定義刪除器?
-
用shared_ptr還是unique_ptr?
1. 什么時(shí)候定義刪除器
自定義刪除器只做一件事,就是將對象重新放入對象池。如果對象池初始化的時(shí)候就自定義刪除器的話,刪除器中的邏輯是將對象放回對象池,放回的時(shí)候無 法再定義一個(gè)這樣的刪除器,所以這種做法行不通。需要注意,回收的對象只能是默認(rèn)刪除器的。除了前述原因之外,另外一個(gè)原因是對象池釋放的時(shí)候需要釋放所 有的智能指針,釋放的時(shí)候如果存在自定義刪除器將會導(dǎo)致對象無法刪除。只有在get的時(shí)候定義刪除器才行,但是初始創(chuàng)建或加入的智能指針是默認(rèn)刪除器,所 以我們需要把智能指針的默認(rèn)刪除器改為自定義刪除器。
1.2 用shared_ptr還是unique_ptr
因?yàn)槲覀冃枰阎悄苤羔樀哪J(rèn)刪除器改為自定義刪除器,用shared_ptr會很不方便,因?yàn)槟銦o法直接將shared_ptr的刪除器修改為自 定義刪除器,雖然你可以通過重新創(chuàng)建一個(gè)新對象,把原對象拷貝過來的做法來實(shí)現(xiàn),但是這樣做效率比較低。而unique_ptr由于是獨(dú)占語義,提供了一 種簡便的方法方法可以實(shí)現(xiàn)修改刪除器,所以用unique_ptr是最適合的。
1.3 實(shí)現(xiàn)源碼
- #pragma once
- #include <memory>
- #include <vector>
- #include <functional>
- template <class T>
- class SimpleObjectPool
- {
- public:
- using DeleterType = std::function<void(T*)>;
- void add(std::unique_ptr<T> t)
- {
- pool_.push_back(std::move(t));
- }
- std::unique_ptr<T, DeleterType> get()
- {
- if (pool_.empty())
- {
- throw std::logic_error("no more object");
- }
- //every time add custom deleter for default unique_ptr
- std::unique_ptr<T, DeleterType> ptr(pool_.back().release(), [this](T* t)
- {
- pool_.push_back(std::unique_ptr<T>(t));
- });
- pool_.pop_back();
- return std::move(ptr);
- }
- bool empty() const
- {
- return pool_.empty();
- }
- size_t size() const
- {
- return pool_.size();
- }
- private:
- std::vector<std::unique_ptr<T>> pool_;
- };
- //test code
- void test_object_pool()
- {
- SimpleObjectPool<A> p;
- p.add(std::unique_ptr<A>(new A()));
- p.add(std::unique_ptr<A>(new A()));
- {
- auto t = p.get();
- p.get();
- }
- {
- p.get();
- p.get();
- }
- std::cout << p.size() << std::endl;
- }
如果你堅(jiān)持用shared_ptr,那么回收的時(shí)候你需要這樣寫:
- std::shared_ptr<T> get()
- {
- if (pool_.empty())
- {
- throw std::logic_error("no more object");
- }
- std::shared_ptr<T> ptr = pool_.back();
- auto p = std::shared_ptr<T>(new T(std::move(*ptr.get())), [this](T* t)
- {
- pool_.push_back(std::shared_ptr<T>(t));
- });
- //std::unique_ptr<T, DeleterType> ptr(pool_.back().release(), [this](T* t)
- //{
- // pool_.push_back(std::unique_ptr<T>(t));
- //});
- pool_.pop_back();
- return p;
- }
這種方式需要每次都創(chuàng)建一個(gè)新對象,并且拷貝原來的對象,是一種比較低效的做法。代碼僅僅是為了展示如何實(shí)現(xiàn)自動回收對象,沒有考慮線程安全、對象池?cái)U(kuò)容策略等細(xì)節(jié),源碼鏈接:object_pool
總結(jié)凡是需要自動回收的場景下都可以使用這種方式:在獲取對象的時(shí)候?qū)⒛J(rèn)刪除器改為自定義刪除器,確保它可以回收。注意,回收的智能指針使用的是 默認(rèn)刪除器,可以確保對象池釋放時(shí)能正常釋放對象。同時(shí)也將獲取對象和釋放對象時(shí),對象的控制權(quán)完全分離。其他的一些應(yīng)用場景:多例模式,無需手動釋放, 自動回收。