詳解C++?智能指針的刪除器
為什么要設(shè)置刪除器
C++11 加入STL的 shared_ptr 和 unique_ptr,已經(jīng)是我們編碼的??土?。用的多自然就會了解到它們的刪除器,比如很多C語言庫(GDAL, GLFW, libcurl等等)創(chuàng)建的指針不能簡單的使用 delete 釋放,當(dāng)我們想使用智能指針管理這些庫創(chuàng)建的資源時(shí),必須設(shè)置刪除器:
//使用重載了operator()的類作為刪除器
struct CurlCleaner
{
void operator()(CURL *ptr) const
{
curl_easy_cleanup(ptr);
}
};
std::unique_ptr<CURL, CurlCleaner> curlu(curl_easy_init(), CurlCleaner{});//第二個(gè)參數(shù)可省略,因?yàn)镃urlCleaner可默認(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);//第二個(gè)參數(shù)必須傳入實(shí)際調(diào)用的函數(shù)地址
std::shared_ptr<GLFWwindow> glfws(glfwCreateWindow(/*省略*/), GLFWClean);
//上述兩個(gè)構(gòu)造函數(shù)中的第二個(gè)參數(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)造,必須傳入一個(gè)實(shí)例
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 的類類型,無需指定刪除器,智能指針會在引用計(jì)數(shù)歸零時(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>());
很長一段時(shí)間內(nèi),我以為智能指針只有 delete 一個(gè)默認(rèn)的刪除器,所以每次在管理 new[] 得到的指針時(shí),都會為它編寫調(diào)用 delete[] 的刪除器,直到翻看智能指針的源碼,發(fā)現(xiàn)它們的默認(rèn)刪除器其實(shí)有一個(gè)針對數(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)庫,查看了一下手頭上的幾個(gè)版本的標(biāo)準(zhǔn)庫實(shí)現(xiàn),發(fā)現(xiàn) unique_ptr 的實(shí)現(xiàn)大致類似。值得一提的是,unique_ptr 自身也有針對數(shù)組形式的特化版本 unique_ptr<_Tp[]>,由于知曉管理的是數(shù)組形式的指針,這個(gè)特化版本不提供 operator-> 訪問符號,取而代之的是 operator[] 來訪問數(shù)組數(shù)據(jù)。
llvm-mingw shared_ptr 默認(rèn)刪除器的選擇是通過輔助模板類 __shared_ptr_default_delete 的特化來實(shí)現(xiàn)的;MSVC 版本中 shared_ptr 的構(gòu)造函數(shù)則直接使用 if consexpr(雖然是 C++17 開始支持,但是發(fā)現(xiàn) C++14 版本的代碼中已經(jīng)使用)判斷實(shí)例化指針類型是否為數(shù)組形式選擇相應(yīng)刪除器;GCC 的 shared_ptr 邏輯相對復(fù)雜一些,其 shared_ptr 繼承自 __shared_ptr,而 __shared_ptr 有一個(gè)類型為 __shared_count 的成員 _M_refcount, 該類有一系列重載的構(gòu)造函數(shù),其中幾個(gè)是:
struct __sp_array_delete
{
template<typename _Yp>
void operator()(_Yp *__p) const
{
delete[] __p;
}
};
r1: template<typename _Ptr>/*默認(rèn)使用delete版本的刪除器,省略實(shí)現(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的接受一個(gè)指針參數(shù)的構(gòu)造函數(shù)
template<typename _Yp, /*檢查_Yp *是否和轉(zhuǎn)換為類的實(shí)例化指針類型*/>
explicit __shared_ptr(_Yp *__p): _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type()){...}通過代碼我們大致可以推測,__shared_count 這個(gè)類是用來管理引用計(jì)數(shù)和刪除器的類。
可以看到,如果 __shared_ptr 構(gòu)造函數(shù)接受的指針類型為普通指針,會調(diào)用 __shared_count(__p, false_type) 將 _M_refcount 構(gòu)造為使用 delete 釋放指針的版本;而當(dāng)它接受的指針類型為數(shù)組形式指針時(shí),__shared_count(__p, true_type) 則會被調(diào)用,構(gòu)造的 _M_refcount 存儲的刪除器是 __sp_array_delete 類型,這個(gè)類型使用 delete[] 釋放指針。
總結(jié)
1.銷毀前需要額外資源釋放操作的類型,使用智能指針管理時(shí)必須設(shè)置自定義刪除器
2.標(biāo)準(zhǔn)庫為智能指針提供了兩個(gè)默認(rèn)版本的刪除器,可簡化智能指針的代碼編寫
到此這篇關(guān)于C++ 智能指針的刪除器的文章就介紹到這了,更多相關(guān)C++ 智能指針內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言實(shí)現(xiàn)個(gè)人財(cái)務(wù)管理軟件
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)個(gè)人財(cái)務(wù)管理軟件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
VC基于ADO技術(shù)訪問數(shù)據(jù)庫的方法
這篇文章主要介紹了VC基于ADO技術(shù)訪問數(shù)據(jù)庫的方法,較為詳細(xì)的分析了VC使用ADO操作數(shù)據(jù)庫的相關(guān)實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
C++使用ffmpeg實(shí)現(xiàn)rtsp取流的代碼
這篇文章主要介紹了C++使用ffmpeg實(shí)現(xiàn)rtsp取流,文章介紹了ffmepg采用rtsp取流流程圖,CMakeLists.txt編寫方法,通過示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04

