一、智能指針的作用:
在C++中,動態內存的管理是用一對運算符完成的:new和delete,new:在動態內存中為對象分配一塊空間並返回一個指向該對象的指針,delete:指向一個動態獨享的指針,銷毀對象,並釋放與之關聯的內存。
動態內存管理經常會出現三種問題:
1、申請之后忘記釋放內存,會造成內存泄漏;
2、另一種是尚有指針引用內存的情況下就釋放了它,就會產生引用非法內存的指針。
3、還有一種是內存的二次釋放,即對同一個指針進行兩次 free() 操作,可能導致程序崩潰
智能指針的作用就是解決上述三種可能出現的問題,指針指針的使用效率不會比一般的指針高,但是它勝在更安全、更穩定
二、智能指針的本質
智能指針的實質是一個類對象,它是利用模板類對一般的指針進行封裝,在類內的構造函數實現對指針的初始化,並在析構函數里編寫delete語句刪除指針指向的內存空間。這樣在程序過期的時候,對象會被刪除,內存會被釋放,實現指針的安全使用。
三、智能指針的類型和使用
智能指針是在C++11版本之后提供,包含在頭文件#include<memory>中,智能指針有四種類型,分別是shared_ptr、unique_ptr、auto_ptr、weak_ptr,這里只介紹前兩種
每種指針都有不同的使用范圍,unique_ptr指針優於其它兩種類型,除非對象需要共享時用shared_ptr。
如果你沒有打算在多個線程之間來共享資源的話,那么就請使用unique_ptr。
1、shared_ptr
shared_ptr可以將多個指針指向相同的對象(共享)。shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存。每使用他一次,對象的引用計數
加1,每析構一次,對象的引用計數減1,減為0時,自動刪除所指向的堆內存。
shared_ptr內部的引用計數是線程安全的,但是對象的讀取需要加鎖。
shared_ptr的初始化
智能指針是個模板類,可以指定類型,傳入指針通過構造函數初始化。也可以使用make_shared函數初始化。不能將指針直接賦值給一個智能指針,一
個是類,一個是指針。例如std::shared_ptr<int> p4 = new int(1);的寫法是錯誤的
shared_ptr的拷貝和賦值
拷貝使得對象的引用計數增加1,賦值使得原始對象引用計數減1,當計數為0時,自動釋放內存。后來指向的對象引用計數加1,指向后來的對象。
shared_ptr傳參的過程就是內存的拷貝,使得對象的引用計數增加1
#include <iostream> #include <memory> using namespace std; shared_ptr<int> func(shared_ptr<int> ps) { (*ps)++; cout<<"ps.use_count()="<<ps.use_count()<<endl; return ps; } int main() { int n=100; //賦值 shared_ptr<int>p=make_shared<int>(n); //拷貝 shared_ptr<int>q (p); cout<<"q.use_count()="<<q.use_count()<<endl; cout<<"p.use_count()="<<p.use_count()<<endl; //傳參 p=func(p); cout<<"p=.use_count()"<<p.use_count()<<endl; cout<<*p<<endl; }
get函數獲取原始指針
我們前面說過,shared_ptr的本質是一個模板類,這里通過get()函數可以獲取它的原始指針
//創建共享指針同時賦值 ClassA是一個類 shared_ptr<ClassA> b = make_shared<ClassA>(100); //b是一個類,c是類指針 ClassA* c=b.get()
注意不要用一個原始指針初始化多個shared_ptr,否則會造成二次釋放同一內存
2、unique_ptr
unique_ptr"唯一"擁有其所指對象,同一時刻只能有一個unique_ptr指向給定對象(通過禁止拷貝語義、只有移動語義來實現)
unique_ptr指針本身的生命周期:從unique_ptr指針創建時開始,直到離開作用域。離開作用域時,若其指向對象,則將其所指對象銷毀(默認使用delete操作
符,用戶可指定其他操作)。
unique_ptr指針與其所指對象的關系:在智能指針生命周期內,可以改變智能指針所指對象,如創建智能指針時通過構造函數指定、
通過reset方法重新指定、通過release方法釋放所有權、通過移動語義轉移所有權。
#include <iostream> #include <memory> int main() { { int a = 10; //動態綁定對象 std::unique_ptr<int> p (new int(a)); std::cout<<*p<<std::endl; //move轉移所有權,轉移之后指針p就變得無效了 std::unique_ptr<int> q=std::move(p); std::cout<<*q<<std::endl; //release釋放所有權,釋放之后指針q變的無效 q.release(); } }
3、weak_ptr()
詳細參考:https://blog.csdn.net/albertsh/article/details/82286999
四、智能指針的設計和實現
上面說過,智能指針的實質就是一個類對象,它是利用模板類對一般的指針進行封裝,在類內的構造函數實現對指針的初始化,增加引用計數,並在析構函數里編寫delete語句刪除指針指向的內存空間。
1、智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針。每次創建類的新對象時,初始化指針並將引用計數置為1;
2、拷貝構造函數:當對象作為另一對象的副本而創建時,拷貝指針並增加與之相應的引用計數;
3、對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(原始指針,如果引用計數為減至0,則刪除對象),並增加右操作數所指對象的引用計數;
4、調用析構函數時,構造函數減少引用計數(如果引用計數減至0,則刪除基礎對象)。
智能指針就是模擬指針動作的類。所有的智能指針都會重載 =、-> 和 * 操作符。
一、shared_ptr()指針的實現
template < class T> class SmartPointer { private: //一般指針 T* _ptr; //計數 size_t* _count; public: //將普通指針ptr封裝成智能指針,ptr為一個普通指針 _ptr(ptr)是構造函數形參列表的初始化賦值操作 SmartPointer(T* ptr = nullptr) : _ptr(ptr) { //如果被初始化的指針ptr不為空指針 if (_ptr) { _count = new size_t(1); } else { _count = new size_t(0); } } //指針指針的拷貝構造 SmartPointer(const SmartPointer& ptr) { if (this != &ptr) { this->_ptr = ptr._ptr; this->_count = ptr._count; (*this->_count)++; } } //賦值,重載=運算符 *this=ptr SmartPointer& operator=(const SmartPointer& ptr) { //判斷是不是自己給自己賦值 if (this->_ptr == ptr._ptr) { return *this; } //等號左邊的對象的 原始指針 計數減一 if (this->_ptr) { (*this->_count)--; if (this->_count == 0) { delete this->_ptr; delete this->_count; } } //復制之后,等號右邊的對象引用計數加一,要注意的是_ptr和_count都是指針 this->_ptr = ptr._ptr; this->_count = ptr._count; (*this->_count)++; return *this; } //重載* T& operator*() { assert(this->_ptr == nullptr); return *(this->_ptr); } //重載-> T* operator->() { assert(this->_ptr == nullptr); return this->_ptr; } //析構函數 ~SmartPointer() { (*this->_count)--; if (*this->_count == 0) { delete this->_ptr; delete this->_count; } } //計數器 size_t use_count() { return *this->_count; } }; int main() { { //新建指針 SmartPointer<int> sp(new int(10)); //拷貝 SmartPointer<int> sp2(sp); //拷貝之后,sp2和sp的引用計數都加一 std::cout << sp2.use_count() << std::endl;//2 std::cout << sp.use_count() << std::endl;//2 //新建 SmartPointer<int> sp3(new int(20)); std::cout << sp2.use_count() << std::endl;//2 std::cout << sp3.use_count() << std::endl;//1 //賦值 sp2 = sp3; //賦值之后,sp3的引用計數加一,sp2的引用計數不變,但是sp2的原始指針的引用計數減一 std::cout << sp.use_count() << std::endl;//1 std::cout << sp2.use_count() << std::endl;//2 std::cout << sp3.use_count() << std::endl;//2 } system("pause"); return 0; }
二、unique_ptr()指針的實現
我們可以在類中把拷貝構造函數和拷貝賦值聲明為private,這樣就不可以對指針指向進行拷貝了,也就不能產生指向同一個對象的指針。
因為把拷貝構造函數和賦值操作符都聲明為delete或private,這樣每一個智能指針要指向一個對象時只能是指向一個新實例化的對象而不能通過“=”或者拷貝去指向前面已經創建了的對象,
例如“unique<A> ptr=&aa”,這里調用了賦值操作符這是不可以的
封裝成unique_ptr()的類包括如下成員函數:
構造函數
析構函數
reset():釋放源資源,指向新資源
release():返回資源,放棄對資源的管理
get():返回資源,只是供外部使用,依然管理資源
operator bool (): 是否持有資源
operator * ()
operator -> ()
拷貝構造函數,禁用,不支持
拷貝賦值函數,禁用,不支持
unique_ptr()指針是如何保證只有一個對象的引用?---------------------把拷貝構造函數和賦值操作符都聲明為delete或private
#include<iostream> using namespace std; template<typename T> class UniquePtr { private: // 禁用拷貝構造 UniquePtr(const UniquePtr &) = delete; // 禁用拷貝賦值 UniquePtr& operator = (const UniquePtr &) = delete; //封裝的普通指針 T *_ptr; //析構函數 void del() { if (nullptr == _ptr) return; delete _ptr; _ptr = nullptr; } public: //構造函數 UniquePtr(T *ptr = NULL) : _ptr(ptr){ } //析構 ~UniquePtr() { //釋放內存私有化 del(); } // 改變指針指向,先釋放資源(如果持有), 再持有資源 void reset(T* ptr) { del(); _ptr = ptr; } // 釋放指針,把當前指針的所有權轉移給調用方 T* release() { T* pTemp = _ptr; _ptr = nullptr; return pTemp; } // 獲取資源,調用方應該只使用不釋放,否則會兩次delete資源 T* get(){ return _ptr; } // 是否持有資源 operator bool() const { return _ptr != nullptr; } T& operator * (){ return *_ptr; } T* operator -> (){ return _ptr; } }; int main() { { //創建指針並綁定對象 UniquePtr<int> sp(new int(10)); //創建空指針 UniquePtr<int> sp1; //綁定對象 sp1.reset(new int(11)); //輸出指針內容 cout << *sp << " " << *sp1 << endl; //把sp指針的所有權轉移給另一個普通指針,但是使用普通指針的時候不要釋放, //否則會多次調用delete內存泄漏,慎用這個方法 int* pp = sp.get(); cout << *pp << " " << *sp << endl; //轉移sp指針 int* p = sp.release(); cout << *p << endl;//sp為空指針 //新建 //UniquePtr<int> sp3(new int(20)); //禁用拷貝構造 //UniquePtr<int> sp2(sp); //禁用賦值 //sp = sp3; } system("pause"); return 0; }
參考博客:https://blog.csdn.net/runner668/article/details/80539221