智能指针shared_ptr
如果有可能就使用unique_ptr,然后很多時候對象是需要共享的,因此shared_ptr也就會用得很多。shared_ptr允許多個指向同一個對象,當指向對象的最后一個shared_ptr銷毀時,該對象也就會自動銷毀。因此,善用shared_ptr,能夠遠離內存泄漏。
基本使用
它的很多操作與unique_ptr類似。下面是三種常見的定義方式:
shared_ptr<int> sp;//聲明一個指向int類型的智能指針 sp.reset(new int(42)); if(sp){cout << "sp is not null" <<endl; } auto sp1 = make_shared<string>("hello");//sp1是一個智能指針 shared_ptr sp2(new int(42));而make_shared方式是推薦的一種,它使用一次分配,比較安全。
不能將一個原始指針直接賦值給一個智能指針,例如,下面這種方法是錯誤的:
std::shared_ptr<int> p = new int(1); //編譯報錯,不允許直接賦值shared_ptr不能通過直接將原始指針賦值來初始化,需要通過構造函數和輔助方法來初始化。對于一個未初始化的智能指針,可以通過reset方法來初始化,當智能指針中有值的時候,調用reset會使引用計數減1。另外,智能指針可以通過重載的bool類型操作符來判斷智能指針中是否為空(未初始化)。
獲取原始指針
當需要獲取原始指針時,可以通過get方法來返回原始指針,代碼如下:
std::shared_ptr<int> ptr(new int(1)); int* p = ptr.get();哪些操作會改變計數
我們都知道,當引用計數為0時,shared_ptr所管理的對象自動銷毀,那么哪些情況會影響引用計數呢?
賦值
例如:
auto sp = make_shared<int>(1024);//sp的引用計數為1再比如:
auto sp1 = make_shared<string>("obj1"); auto sp2 = make_shared<string>("obj2"); auto sp1 = sp2;該操作會減少sp1的引用計數,增加sp2的引用計數。有的人可能不理解,為什么這樣還會減少sp1的引用計數?試想一下,sp1指向對象obj1,sp2指向對象obj2,那么賦值之后,sp1也會指向obj2,那就是說指向obj1的就少了,指向obj2的就會多,如果此時沒有其他shared_ptr指向obj1,那么obj1將會銷毀。
拷貝
例如:
auto sp2 = make_shared<int>(1024); auto sp1(sp2);該操作會使得sp1和sp2都指向同一個對象。而關于拷貝比較容易忽略的就是作為參數傳入函數:
auto sp2 = make_shared<int>(1024); func(sp2);//func的執行會增加其引用計數可以看一個具體的例子:
#include<iostream> #include<memory> void func0(std::shared_ptr<int> sp) {std::cout<<"fun0:"<<sp.use_count()<<std::endl; } void func1(std::shared_ptr<int> &sp) {std::cout<<"fun1:"<<sp.use_count()<<std::endl; } int main() {auto sp = std::make_shared<int>(1024);func0(sp);func1(sp);return 0; }其運行輸出結果為:
fun0:2 fun1:1很顯然,fun0,拷貝了shard_ptr sp,而fun1,并沒有拷貝,因此前者會增加引用計數,計數變為2,而后者并不影響。
reset
調用reset會減少計數:
sp.reset()而如果sp是唯一指向該對象的,則該對象被銷毀。
應當注意使用的方式
雖然shared_ptr能很大程度避免內存泄漏,但是使用不當,仍然可能導致意外發生。
存放于容器中的shared_ptr
如果你的容器中存放的是shared_ptr,而你后面又不再需要它時,記得使用erase刪除那些不要的元素,否則由于引用計數一直存在,其對象將始終得不到銷毀,除非容器本身被銷毀。
不要使用多個裸指針初始化多個shared_ptr
注意,下面方式是不該使用的:
#include<iostream> #include<memory> int main() {auto *p = new std::string("hello");std::shared_ptr<std::string> sp1(p);/*不要這樣做!!*/std::shared_ptr<std::string> sp2(p);return 0; }這樣會導致兩個shared_ptr管理同一個對象,當其中一個被銷毀時,其管理的對象會被銷毀,而另外一個銷毀時,對象會二次銷毀,然而實際上,對象已經不在了,最終造成嚴重后果。而與這種情況類似的,就是使用get()獲取裸指針,然后去初始化另外一個shared_ptr,或者delete get返回的指針:
#include<iostream> #include<memory> int main() {auto sp = std::make_shared<std::string>("wechat:shouwangxiansheng");std::string *p = sp.get();std::shared_ptr<std::string> sp2(p);/*不要這樣做!!*/delete p;/*不要這樣做*/return 0; }循環引用
在對象之間出現循環引用時,會使得共享指針引用計數不會降到0,也就不能銷毀。
?
#include <iostream> #include <memory> using std::shared_ptr; using std::make_shared; // 一段內存泄露的代碼 struct Son; struct Father{shared_ptr<Son> son_; }; struct Son{shared_ptr<Father> father_; }; int main() {auto father = make_shared<Father>();auto son = make_shared<Son>();father->son_ = son;son->father_ = father;std::cout<<"one father's son:"<<father.use_count()<<std::endl; std::cout<<"one son's father:"<<son.use_count()<<std::endl; return 0; }編譯運行結果為:
xhy@ubuntu:~/cpp_learn/share_ptr$ ./test one father's son:2 one son's father:2函數結束前,堆上的兩個對象的引用計數都是2,所以即便函數結束,將兩個棧上的的共享指針分別析構,最后堆上的兩個對象的引用數也不會為0,而是1,兩個對象不會調用析構函數進行析構,從而內存泄漏。參考weak_ptr可以解決。
如果對象不是new分配的,請傳遞刪除器
與unique_ptr類似,它可以指定刪除器,默認是使用delete。例如:
#include<iostream> #include<unistd.h> #include<memory> void myClose(int *fd) {close(*fd); } int main() {int socketFd = 10;//just for examplestd::shared_ptr<int> up(&socketFd,myClose);return 0; }關于指定刪除器
智能指針初始化可以指定刪除器,代碼如下:
void DeleteIntPtr(int* p) {delete p; } std::shared_ptr<int> p(new int,DeleteIntPtr);當p的引用計數為0時,自動調用刪除器DeleteIntPtr來釋放對象的內存。刪除器可以是一個lambda表達式,因此,上面的寫法還可以改為:
std::shared_ptr<int> p(new int,[](int* p){delete p;});當我們用shared_ptr管理動態數組時,需要指定刪除器,因為std::shared_ptr的默認刪除器不支持數組對象,代碼如下:
std::shared_ptr<int> p(new int[10],[](int* p){delete[] p;}); //指定delete[]也可以將std::default_delete作為刪除器。default_delete的內部是通過調用delete來實現功能的,代碼如下:
std::shared_ptr<int> p(new int[10],std::default_delete <int[]>);另外,還可以通過封裝一個make_shared_array方法來讓shared_ptr支持數組,代碼如下:
template<typename> shared_ptr<T> make_shared_array(size_t size) {return shared_ptr<T>(new T[size],default_delete<T[]>()); }測試代碼如下:
std::shared_ptr<int> p = make_shared_array<int>(10); std::shared_ptr<char> p = make_shared_array<char>(10);與unique_ptr的區別
首先最明顯的區別自然是它們一個是專享對象,一個是共享對象。而正是由于共享,包括要維護引用計數等,它帶來的開銷相比于unique_ptr來說要大。另外,shared_ptr無法直接處理數組,因為它使用delete來銷毀對象,而對于數組,需要用delete[]。因此,需要指定刪除器:
#include<iostream> #include<memory> int main() {auto sp = std::make_shared<std::string>("wechat:shouwangxiansheng");std::string *p = sp.get();//std::shared_ptr<int> sp1(new int[10]);//不能這樣std::shared_ptr<int> sp1(new int[10],[](int *p){delete[] p;});return 0; }示例中使用了lambda表達式。不過一般來說,好好的容器不用,為什么要用動態數組呢?
參考資料:https://xhy3054.github.io/cpp-shared-ptr/
總結
以上就是shared_ptr基本內容,一般來說,規范使用shared_ptr能很大程度避免內存泄露。注意,shared_ptr提供,*,->操作,不直接提供指針運算和[]。
總結
以上是生活随笔為你收集整理的智能指针shared_ptr的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用C++11 实现 thread poo
- 下一篇: 智能指针weak_ptr