詳解C++?智能指針的刪除器
為什么要設(shè)置刪除器
C++11 加入STL的 shared_ptr 和 unique_ptr,已經(jīng)是我們編碼的??土恕S玫亩嘧匀痪蜁私獾剿鼈兊膭h除器,比如很多C語言庫(GDAL, GLFW, libcurl等等)創(chuàng)建的指針不能簡單的使用 delete 釋放,當(dāng)我們想使用智能指針管理這些庫創(chuàng)建的資源時,必須設(shè)置刪除器:
//使用重載了operator()的類作為刪除器 struct CurlCleaner { void operator()(CURL *ptr) const { curl_easy_cleanup(ptr); } }; std::unique_ptr<CURL, CurlCleaner> curlu(curl_easy_init(), CurlCleaner{});//第二個參數(shù)可省略,因為CurlCleaner可默認(rèn)構(gòu)造 std::shared_ptr<CURL> curls(curl_easy_init(), CurlCleaner{}); //使用函數(shù)指針作為刪除器 void GLFWClean(GLFWwindow *wnd) { glfwDestroyWindow(wnd); } std::unique_ptr<GLFWwindow, decltype(&GLFWClean)> glfwu(glfwCreateWindow(/*省略*/), GLFWClean);//第二個參數(shù)必須傳入實際調(diào)用的函數(shù)地址 std::shared_ptr<GLFWwindow> glfws(glfwCreateWindow(/*省略*/), GLFWClean); //上述兩個構(gòu)造函數(shù)中的第二個參數(shù)都進(jìn)行了函數(shù)名到函數(shù)指針的隱式轉(zhuǎn)換 //使用lambda作為刪除器 auto GDALClean=[](GDALDataset *dataset){ GDALClose(dataset); }; std::unique_ptr<GDALDataset, decltype(GDALClean)> gdalu(GDALOpen(/*省略*/), GDALClean);//lambda無法默認(rèn)構(gòu)造,必須傳入一個實例 std::shared_ptr<GDALDataset> gdals(GDALOpen(/*省略*/), GDALClean);
上面是三種最常使用的自定義刪除器形式,也可以利用 std::function 的強(qiáng)大適配能力來包裝可調(diào)用對象作為刪除器,此處不展開。
標(biāo)準(zhǔn)庫提供的默認(rèn)刪除器
內(nèi)置類型和析構(gòu)函數(shù)為 public 的類類型,無需指定刪除器,智能指針會在引用計數(shù)歸零時自動調(diào)用 delete 對管理的指針進(jìn)行釋放,使得語法相對簡潔:
std::unique_ptr<int> pi(new int(42)); std::shared_ptr<float> pf(new float(0.0f)); std::unique_ptr<std::vector<int>> pveci(new std::vector<int>()); std::unique_ptr<std::list<int>> plsti(new std::list<int>());
很長一段時間內(nèi),我以為智能指針只有 delete 一個默認(rèn)的刪除器,所以每次在管理 new[] 得到的指針時,都會為它編寫調(diào)用 delete[] 的刪除器,直到翻看智能指針的源碼,發(fā)現(xiàn)它們的默認(rèn)刪除器其實有一個針對數(shù)組形式指針的特化版本:
template<class _Tp> struct default_delete//默認(rèn)刪除器主模板 { ... void operator()(_Tp *_Ptr) const noexcept { ... delete _Ptr;//使用delete釋放指針 } ... } template<class _Tp> struct default_delete<_Tp[]>//針對數(shù)組形式的特化版本 { ... void operator()(_Tp *_Ptr) const noexcept { ... delete[] _Ptr;//使用delete[]釋放指針 } ... } //unique_ptr template<class _Tp, class _Dp = default_delete<_Tp>/*默認(rèn)刪除器*/> class unique_ptr{...}; //shared_ptr template <class, class _Yp>//輔助類主模板,普通指針應(yīng)用該版本 struct __shared_ptr_default_delete : default_delete<_Yp> {}; template <class _Yp, class _Un, size_t _Sz>//數(shù)組形式特化,匹配固定長度的數(shù)組形式,如std::shared_ptr<int[10]> struct __shared_ptr_default_delete<_Yp[_Sz], _Un> : default_delete<_Yp[]> {}; template <class _Yp, class _Un>//數(shù)組形式特化,匹配不定長度的數(shù)組形式,如std::shared_ptr<int[]> struct __shared_ptr_default_delete<_Yp[], _Un> : default_delete<_Yp[]> {}; template<class _Tp> class shared_ptr { ... template <class _Yp,/*檢查_Yp指針是否可轉(zhuǎn)換為_Tp指針(比如子類指針到基類指針)、_Yp類型是否可應(yīng)用delete與delete[]操作*/> explicit shared_ptr(_Yp* __p) : __ptr_(__p) { ... typedef __shared_ptr_pointer<_Yp*, __shared_ptr_default_delete<_Tp, _Yp>/*根據(jù)_Yp類型選擇合適的默認(rèn)刪除器*/, _AllocT> _CntrlBlk; __cntrl_ = new _CntrlBlk(__p, __shared_ptr_default_delete<_Tp, _Yp>(), _AllocT()); ... } ... }; //用戶代碼 std::unique_ptr<int[]> piu(new int[10]);//匹配int[]版本,刪除器編譯為使用delete[]釋放指針 std::shared_ptr<int[]> pis(new int[10]);//構(gòu)造函數(shù)內(nèi)選擇使用delete[]釋放指針的刪除器
以上代碼節(jié)選自 llvm-mingw 的標(biāo)準(zhǔn)庫,查看了一下手頭上的幾個版本的標(biāo)準(zhǔn)庫實現(xiàn),發(fā)現(xiàn) unique_ptr 的實現(xiàn)大致類似。值得一提的是,unique_ptr 自身也有針對數(shù)組形式的特化版本 unique_ptr<_Tp[]>,由于知曉管理的是數(shù)組形式的指針,這個特化版本不提供 operator-> 訪問符號,取而代之的是 operator[] 來訪問數(shù)組數(shù)據(jù)。
llvm-mingw shared_ptr 默認(rèn)刪除器的選擇是通過輔助模板類 __shared_ptr_default_delete 的特化來實現(xiàn)的;MSVC 版本中 shared_ptr 的構(gòu)造函數(shù)則直接使用 if consexpr(雖然是 C++17 開始支持,但是發(fā)現(xiàn) C++14 版本的代碼中已經(jīng)使用)判斷實例化指針類型是否為數(shù)組形式選擇相應(yīng)刪除器;GCC 的 shared_ptr 邏輯相對復(fù)雜一些,其 shared_ptr 繼承自 __shared_ptr,而 __shared_ptr 有一個類型為 __shared_count 的成員 _M_refcount, 該類有一系列重載的構(gòu)造函數(shù),其中幾個是:
struct __sp_array_delete { template<typename _Yp> void operator()(_Yp *__p) const { delete[] __p; } }; r1: template<typename _Ptr>/*默認(rèn)使用delete版本的刪除器,省略實現(xiàn)*/ explicit __shared_count(_Ptr __p) r2: template<typename _Ptr>/*委托給r1*/ __shared_count(_Ptr __P, false_type) : __shared_count(__p){} r3: template<typename _Ptr, typename _Deleter, typename _Alloc, typename = typename __not_alloc_shared_tag<_Deleter>::type> __shared_count(_Ptr __p, _Deleter __d, _Alloc __a)/*可指定刪除器、內(nèi)存分配器的版本*/ r4: template<typename _Ptr>/*委托給r3*/ __shared_count(_Ptr __p, true_type) : __shared_count(__p, __sp_array_delete{}, allocator<void>()){} //__shared_ptr的接受一個指針參數(shù)的構(gòu)造函數(shù) template<typename _Yp, /*檢查_Yp *是否和轉(zhuǎn)換為類的實例化指針類型*/> explicit __shared_ptr(_Yp *__p): _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type()){...}
通過代碼我們大致可以推測,__shared_count 這個類是用來管理引用計數(shù)和刪除器的類。
可以看到,如果 __shared_ptr 構(gòu)造函數(shù)接受的指針類型為普通指針,會調(diào)用 __shared_count(__p, false_type) 將 _M_refcount 構(gòu)造為使用 delete 釋放指針的版本;而當(dāng)它接受的指針類型為數(shù)組形式指針時,__shared_count(__p, true_type) 則會被調(diào)用,構(gòu)造的 _M_refcount 存儲的刪除器是 __sp_array_delete 類型,這個類型使用 delete[] 釋放指針。
總結(jié)
1.銷毀前需要額外資源釋放操作的類型,使用智能指針管理時必須設(shè)置自定義刪除器
2.標(biāo)準(zhǔn)庫為智能指針提供了兩個默認(rèn)版本的刪除器,可簡化智能指針的代碼編寫
到此這篇關(guān)于C++ 智能指針的刪除器的文章就介紹到這了,更多相關(guān)C++ 智能指針內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VC基于ADO技術(shù)訪問數(shù)據(jù)庫的方法
這篇文章主要介紹了VC基于ADO技術(shù)訪問數(shù)據(jù)庫的方法,較為詳細(xì)的分析了VC使用ADO操作數(shù)據(jù)庫的相關(guān)實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10