C++共享智能指針shared_ptr的實現(xiàn)
共享智能指針
在C++中沒有垃圾回收機制,必須自己釋放分配的內(nèi)存,否則就會造成內(nèi)存泄露。解決這個問題最有效的方法是使用智能指針(smart pointer)。智能指針是存儲指向動態(tài)分配(堆)對象指針的類,用于生存期的控制,能夠確保在離開指針?biāo)谧饔糜驎r,自動地銷毀動態(tài)分配的對象,防止內(nèi)存泄露。智能指針的核心實現(xiàn)技術(shù)是引用計數(shù),每使用它一次,內(nèi)部引用計數(shù)加1,每析構(gòu)一次內(nèi)部的引用計數(shù)減1,減為0時,刪除所指向的堆內(nèi)存。
C++11中提供了三種智能指針,使用這些智能指針時需要引用頭文件:
- std::shared_ptr:共享的智能指針
- std::unique_ptr:獨占的智能指針
- std::weak_ptr:弱引用的智能指針,它不共享指針,不能操作資源,是用來監(jiān)視shared_ptr的。
1.shared_ptr的初始化
共享智能指針是指多個智能指針可以同時管理同一塊有效的內(nèi)存,共享智能指針shared_ptr 是一個模板類,如果要進行初始化有三種方式:通過構(gòu)造函數(shù)、std::make_shared輔助函數(shù)以及reset方法。共享智能指針對象初始化完畢之后就指向了要管理的那塊堆內(nèi)存,如果想要查看當(dāng)前有多少個智能指針同時管理著這塊內(nèi)存可以使用共享智能指針提供的一個成員函數(shù)use_count,函數(shù)原型如下:
// 管理當(dāng)前對象的 shared_ptr 實例數(shù)量,或若無被管理對象則為 0。 long use_count() const noexcept;
1.1 通過構(gòu)造函數(shù)初始化
// shared_ptr<T> 類模板中,提供了多種實用的構(gòu)造函數(shù), 語法格式如下: std::shared_ptr<T> 智能指針名字(創(chuàng)建堆內(nèi)存);
測試代碼如下:
#include <iostream> #include <memory> using namespace std; int main() { // 使用智能指針管理一塊 int 型的堆內(nèi)存 shared_ptr<int> ptr1(new int(520)); cout << "ptr1管理的內(nèi)存引用計數(shù): " << ptr1.use_count() << endl; // 使用智能指針管理一塊字符數(shù)組對應(yīng)的堆內(nèi)存 shared_ptr<char> ptr2(new char[12]); cout << "ptr2管理的內(nèi)存引用計數(shù): " << ptr2.use_count() << endl; // 創(chuàng)建智能指針對象, 不管理任何內(nèi)存 shared_ptr<int> ptr3; cout << "ptr3管理的內(nèi)存引用計數(shù): " << ptr3.use_count() << endl; // 創(chuàng)建智能指針對象, 初始化為空 shared_ptr<int> ptr4(nullptr); cout << "ptr4管理的內(nèi)存引用計數(shù): " << ptr4.use_count() << endl; return 0; }
測試代碼輸出的結(jié)果如下:
ptr1管理的內(nèi)存引用計數(shù): 1
ptr2管理的內(nèi)存引用計數(shù): 1
ptr3管理的內(nèi)存引用計數(shù): 0
ptr4管理的內(nèi)存引用計數(shù): 0
如果智能指針被初始化了一塊有效內(nèi)存,那么這塊內(nèi)存的引用計數(shù)+1,如果智能指針沒有被初始化或者被初始化為nullptr空指針,引用計數(shù)不會+1。另外,不要使用一個原始指針初始化多個shared_ptr。
int *p = new int; shared_ptr<int> p1(p); shared_ptr<int> p2(p); // error, 編譯不會報錯, 運行會出錯
1.2 通過拷貝和移動構(gòu)造函數(shù)初始化
當(dāng)一個智能指針被初始化之后,就可以通過這個智能指針初始化其他新對象。在創(chuàng)建新對象的時候,對應(yīng)的拷貝構(gòu)造函數(shù)或者移動構(gòu)造函數(shù)就被自動調(diào)用了。
void test02() { // 使用智能指針管理一塊 int 型的堆內(nèi)存, 內(nèi)部引用計數(shù)為 1 shared_ptr<int> ptr1(new int(520)); cout << "ptr1管理的內(nèi)存引用計數(shù): " << ptr1.use_count() << endl; //調(diào)用拷貝構(gòu)造函數(shù) shared_ptr<int> ptr2(ptr1); cout << "ptr2管理的內(nèi)存引用計數(shù): " << ptr2.use_count() << endl; shared_ptr<int> ptr3 = ptr1; cout << "ptr3管理的內(nèi)存引用計數(shù): " << ptr3.use_count() << endl; //調(diào)用移動構(gòu)造函數(shù) shared_ptr<int> ptr4(std::move(ptr1)); cout << "ptr4管理的內(nèi)存引用計數(shù): " << ptr4.use_count() << endl; std::shared_ptr<int> ptr5 = std::move(ptr2); cout << "ptr5管理的內(nèi)存引用計數(shù): " << ptr5.use_count() << endl; }
測試程序輸入的結(jié)果:
ptr1管理的內(nèi)存引用計數(shù): 1
ptr2管理的內(nèi)存引用計數(shù): 2
ptr3管理的內(nèi)存引用計數(shù): 3
ptr4管理的內(nèi)存引用計數(shù): 3
ptr5管理的內(nèi)存引用計數(shù): 3
如果使用拷貝的方式初始化共享智能指針對象,這兩個對象會同時管理同一塊堆內(nèi)存,堆內(nèi)存對應(yīng)的引用計數(shù)也會增加;如果使用移動的方式初始智能指針對象,只是轉(zhuǎn)讓了內(nèi)存的所有權(quán),管理內(nèi)存的對象并不會增加,因此內(nèi)存的引用計數(shù)不會變化。
1.3 通過std::make_shared初始化
通過C++提供的std::make_shared() 就可以完成內(nèi)存對象的創(chuàng)建并將其初始化給智能指針,函數(shù)源碼如下:
template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args) { return std::allocate_shared<T>(std::allocator<T>(), std::forward<Args>(args)...); }
這個函數(shù)模板使用了可變模板參數(shù)(variadic templates
)和完美轉(zhuǎn)發(fā)(perfect forwarding
)的特性。它接受一個類型 T
和任意數(shù)量的參數(shù)(Args),然后調(diào)用 std::allocate_shared 來完成共享指針的初始化和對象的構(gòu)造。
可變模板參數(shù)是指函數(shù)模板的參數(shù)數(shù)量是可變的。在std::make_shared
的情況下,它允許我們以任意數(shù)量的參數(shù)初始化對象。
舉例來說,假設(shè)有一個類 MyClass:
class MyClass { public: MyClass(int a, double b, const std::string& c) { // 構(gòu)造函數(shù)的實現(xiàn) } };
可以使用 std::make_shared
來創(chuàng)建一個 shared_ptr
并調(diào)用 MyClass
的構(gòu)造函數(shù):
#include <memory> #include <string> int main() { auto mySharedPtr = std::make_shared<MyClass>(42, 3.14, "Hello"); // 使用 mySharedPtr return 0; }
在這個例子中,MyClass
有一個帶有三個參數(shù)的構(gòu)造函數(shù),而 std::make_shared
允許你提供這三個參數(shù),以便在分配內(nèi)存時調(diào)用構(gòu)造函數(shù)進行對象初始化??勺兡0鍏?shù)的使用使得 std::make_shared
能夠處理任意數(shù)量的構(gòu)造函數(shù)參數(shù)。
測試代碼如下:
#include <iostream> #include <string> #include <memory> using namespace std; class Test { public: Test() { cout << "construct Test..." << endl; } Test(int x) { cout << "construct Test, x = " << x << endl; } Test(string str) { cout << "construct Test, str = " << str << endl; } ~Test() { cout << "destruct Test ..." << endl; } }; int main() { // 使用智能指針管理一塊 int 型的堆內(nèi)存, 內(nèi)部引用計數(shù)為 1 shared_ptr<int> ptr1 = make_shared<int>(520); cout << "ptr1管理的內(nèi)存引用計數(shù): " << ptr1.use_count() << endl; shared_ptr<Test> ptr2 = make_shared<Test>(); cout << "ptr2管理的內(nèi)存引用計數(shù): " << ptr2.use_count() << endl; shared_ptr<Test> ptr3 = make_shared<Test>(520); cout << "ptr3管理的內(nèi)存引用計數(shù): " << ptr3.use_count() << endl; shared_ptr<Test> ptr4 = make_shared<Test>("我是要成為海賊王的男人!!!"); cout << "ptr4管理的內(nèi)存引用計數(shù): " << ptr4.use_count() << endl; return 0; }
測試代碼輸出:
ptr1管理的內(nèi)存引用計數(shù): 1
construct Test...
ptr2管理的內(nèi)存引用計數(shù): 1
construct Test, x = 520
ptr3管理的內(nèi)存引用計數(shù): 1
construct Test, str = 我是要成為海賊王的男人!!!
ptr4管理的內(nèi)存引用計數(shù): 1
destruct Test ...
destruct Test ...
destruct Test ...
使用std::make_shared()模板函數(shù)可以完成內(nèi)存地址的創(chuàng)建,并將最終得到的內(nèi)存地址傳遞給共享智能指針對象管理。如果申請的內(nèi)存是普通類型,通過函數(shù)的()可完成地址的初始化,如果要創(chuàng)建一個類對象,函數(shù)的()內(nèi)部需要指定構(gòu)造對象需要的參數(shù),也就是類構(gòu)造函數(shù)的參數(shù)。
1.4 通過reset方法初始化
共享智能指針類提供的std::shared_ptr::reset方法函數(shù)原型如下:
void reset() noexcept; template< class Y > void reset( Y* ptr ); template< class Y, class Deleter > void reset( Y* ptr, Deleter d ); template< class Y, class Deleter, class Alloc > void reset( Y* ptr, Deleter d, Alloc alloc );
- ptr:指向要取得所有權(quán)的對象的指針
- d:自定義的刪除器(deleter)
- aloc:自定義的分配器
其中最常見的是不帶任何參數(shù)的形式和帶一個指針參數(shù)的形式。
不帶參數(shù)的reset
template<class Y> void reset();
這個版本的 reset
將 shared_ptr
置為空,即它不再擁有任何對象。如果此時 shared_ptr
是最后一個擁有某個對象的智能指針,它會調(diào)用對象的析構(gòu)函數(shù)來釋放資源。
std::shared_ptr<int> ptr = std::make_shared<int>(42); ptr.reset(); // ptr 現(xiàn)在為空,且釋放了之前的對象
帶指針參數(shù)的reset
template<class Y> void reset(Y* ptr);
這個版本的 reset
會替換 shared_ptr
當(dāng)前所管理的對象,并且使用參數(shù) ptr
所指向的對象。如果此時 shared_ptr
是最后一個擁有某個對象的智能指針,它會調(diào)用原對象的析構(gòu)函數(shù)來釋放資源,然后開始管理新的對象。
std::shared_ptr<int> ptr = std::make_shared<int>(42); ptr.reset(new int(10)); // ptr 不再指向之前的對象,而是指向一個新的 int 對象
帶指針和自定義刪除器的 reset
:
template<class Y, class Deleter> void reset(Y* ptr, Deleter deleter);
這個版本的 reset
允許你指定一個自定義的刪除器(deleter),該刪除器將在 shared_ptr
管理的對象銷毀時被調(diào)用。這允許你使用 shared_ptr
管理通過非默認方式分配的資源。
std::shared_ptr<int> ptr(new int, [](int* p) { delete p; }); ptr.reset(new int, [](int* p) { delete[] p; });
帶指針、自定義刪除器和分配器的 reset
:
template<class Y, class Deleter, class Alloc> void reset(Y* ptr, Deleter deleter, Alloc alloc);
這個版本的 reset
允許你指定自定義的刪除器和分配器。通常情況下,std::shared_ptr
使用默認分配器 std::allocator
和默認刪除器 delete
。
std::shared_ptr<int> ptr(new int, [](int* p) { delete p; }, std::allocator<int>());
來看一下這個例子:
#include <iostream> #include <string> #include <memory> using namespace std; int main() { // 使用智能指針管理一塊 int 型的堆內(nèi)存, 內(nèi)部引用計數(shù)為 1 shared_ptr<int> ptr1 = make_shared<int>(520); shared_ptr<int> ptr2 = ptr1; shared_ptr<int> ptr3 = ptr1; shared_ptr<int> ptr4 = ptr1; cout << "ptr1管理的內(nèi)存引用計數(shù): " << ptr1.use_count() << endl; // 4 cout << "ptr2管理的內(nèi)存引用計數(shù): " << ptr2.use_count() << endl; // 4 cout << "ptr3管理的內(nèi)存引用計數(shù): " << ptr3.use_count() << endl; // 4 cout << "ptr4管理的內(nèi)存引用計數(shù): " << ptr4.use_count() << endl; // 4 ptr4.reset(); cout << "ptr4管理的內(nèi)存引用計數(shù): " << ptr4.use_count() << endl; // 0 ptr4.reset(new int(250)); cout << "ptr1管理的內(nèi)存引用計數(shù): " << ptr1.use_count() << endl; // 3 cout << "ptr2管理的內(nèi)存引用計數(shù): " << ptr2.use_count() << endl; // 3 cout << "ptr3管理的內(nèi)存引用計數(shù): " << ptr3.use_count() << endl; // 3 cout << "ptr4管理的內(nèi)存引用計數(shù): " << ptr4.use_count() << endl; // 1 return 0; }
對于一個未初始化的共享智能指針,可以通過reset方法來初始化,當(dāng)智能指針中有值的時候,調(diào)用reset會使引用計數(shù)減1。
1.5 獲取原始指針
通過智能指針可以管理一個普通變量或者對象的地址,此時原始地址就不可見了。當(dāng)我們想要修改變量或者對象中的值的時候,就需要從智能指針對象中先取出數(shù)據(jù)的原始內(nèi)存的地址再操作,解決方案是調(diào)用共享智能指針類提供的get()方法,其函數(shù)原型如下:
T* get() const noexcept;
測試代碼如下:
#include <iostream> #include <string> #include <memory> using namespace std; int main() { int len = 128; shared_ptr<char> ptr(new char[len]); // 得到指針的原始地址 char* add = ptr.get(); fill(ptr.get(), ptr.get() + len, '\0'); const char* source = "我愛cpp"; copy(source, source + strlen(source) + 1, ptr.get()); cout << "string: " << ptr.get() << endl; shared_ptr<int> p(new int); *p = 100; cout << *p.get() << " " << *p << endl; // 100 100 return 0; }
2.指定刪除器
當(dāng)智能指針管理的內(nèi)存對應(yīng)的引用計數(shù)變?yōu)?的時候,這塊內(nèi)存就會被智能指針析構(gòu)掉了。另外,我們在初始化智能指針的時候也可以自己指定刪除動作,這個刪除操作對應(yīng)的函數(shù)被稱之為刪除器,這個刪除器函數(shù)本質(zhì)是一個回調(diào)函數(shù),我們只需要進行實現(xiàn),其調(diào)用是由智能指針完成的。
#include <iostream> #include <memory> using namespace std; // 自定義刪除器函數(shù),釋放int型內(nèi)存 void deleteIntPtr(int* p) { delete p; cout << "int 型內(nèi)存被釋放了..."; } int main() { shared_ptr<int> ptr(new int(250), deleteIntPtr); return 0; }
刪除器函數(shù)也可以是lambda表達式,因此代碼也可以寫成下面這樣:
int main() { shared_ptr<int> ptr(new int(250), [](int* p) {delete p; }); return 0; }
在上面的代碼中,lambda表達式的參數(shù)就是智能指針管理的內(nèi)存的地址,有了這個地址之后函數(shù)體內(nèi)部就可以完成刪除操作了。
管理動態(tài)數(shù)組
在C++11中使用shared_ptr管理動態(tài)數(shù)組時,需要指定刪除器,因為std::shared_ptr的默認刪除器不支持?jǐn)?shù)組對象,具體的處理代碼如下:
int main() { shared_ptr<int> ptr(new int[10], [](int* p) {delete[]p; }); return 0; }
在刪除數(shù)組內(nèi)存時,除了自己編寫刪除器,也可以使用C++提供的std::default_delete()函數(shù)作為刪除器,這個函數(shù)內(nèi)部的刪除功能也是通過調(diào)用delete來實現(xiàn)的,要釋放什么類型的內(nèi)存就將模板類型T指定為什么類型即可。具體處理代碼如下:
int main() { shared_ptr<int> ptr(new int[10], default_delete<int[]>()); return 0; }
另外,我們還可以自己封裝一個make_shared_array方法來讓shared_ptr支持?jǐn)?shù)組,代碼如下:
#include <iostream> #include <memory> using namespace std; template <typename T> shared_ptr<T> make_share_array(size_t size) { // 返回匿名對象 return shared_ptr<T>(new T[size], default_delete<T[]>()); } int main() { shared_ptr<int> ptr1 = make_share_array<int>(10); cout << ptr1.use_count() << endl; shared_ptr<char> ptr2 = make_share_array<char>(128); cout << ptr2.use_count() << endl; return 0; }
資源的所有權(quán)轉(zhuǎn)移
另一方面: 指定刪除器也用于資源的所有權(quán)轉(zhuǎn)移
刪除器允許你將資源的所有權(quán)轉(zhuǎn)移給智能指針。這對于使用自定義釋放邏輯的資源管理類很有幫助,因為你可以將資源的釋放責(zé)任委托給智能指針。
class CustomResource { public: CustomResource(int* data) : data_(data) {} void release() { delete[] data_; } private: int* data_; }; // 將自定義資源類的釋放函數(shù)作為刪除器 std::shared_ptr<CustomResource> customPtr(new CustomResource(new int[10]), [](CustomResource* resource) { resource->release(); delete resource; });
到此這篇關(guān)于C++共享智能指針shared_ptr的實現(xiàn)的文章就介紹到這了,更多相關(guān)C++ shared_ptr內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
HDOJ 1443 約瑟夫環(huán)的最新應(yīng)用分析詳解
本篇文章是對HDOJ 1443 約瑟夫環(huán)的最新應(yīng)用進行了詳細的分析介紹,需要的朋友參考下2013-05-05如何用c++表驅(qū)動替換if/else和switch/case語句
本文將介紹使用表驅(qū)動法,替換復(fù)雜的if/else和switch/case語句,想了解詳細內(nèi)容,請看下文2021-08-08