1、shared_ptr共享智能指針
std::shared_ptr使用引用計數,每個shared_ptr的拷貝都指向相同的內存,在最后一個shared_ptr析構的時候,內存才會釋放。
1.1 基本用法
1.1.1 初始化
shared_ptr可以通過make_shared來初始化,也可以通過shared_ptr<T>輔助函數和reset方法來初始化。智能指針的用法和普通指針的用法類似,不過不需要自己管理分配的內存,對於沒有初始化的指針,只能通過reset來初始化,當智能指針有值,reset會使計數器減1。智能指針可以通過重載的bool來判斷是否為空。
#include <iostream> #include <memory> using namespace std; int main() { //智能指針初始化 shared_ptr<int> p = make_shared<int>(20); shared_ptr<int> p(new int(1)); shared_ptr<int> p1 = p; shared_ptr<int> ptr; //所指的對象會被重置,不帶參數則是銷毀 ptr.reset(new int(5)); if(ptr) { cout << "ptr is not null" << endl; } return 0; }
智能指針不能通過原始指針來初始化:
shared_ptr<int> p = new int(1); //編譯報錯,不能直接賦值
1.1.2 獲取原始指針
當需要獲取原始指針的時候,可以通過get來返回原始指針。不能釋放,如果釋放會出錯。
shared_ptr<int> ptr(new int(1)); int* p = ptr.get(); delete p; //error
1.1.3 指定刪除器
智能指針支持指定刪除器,在指針引用為0的時候自動調用。支持普通函數和lambda表達式。
//普通函數 void DeleteIntPtr(int *p) {delete p;} shared_ptr<int> p(new int(10), DeleteIntPtr);
//lambda表達式 shared_ptr<int> p(new int(10), [](int *p) {delete p;});
當智能指針管理動態數組的時候,默認的刪除器不支持數組對象。需要指定刪除器,自定義刪除器或者使用改善的默認修改器都可以。
shared_ptr<int> p(new int[10], [](int *p) {delete[] p;}); //lambda shared_ptr<int> p1(new int[10], default_delete<int []>); //指定delete []
1.2 注意問題
a.避免一個原始指針初始化多個shared_ptr。
int* p = new int; shared_ptr<int> p1(p); shared_ptr<int> p2(p);
b.不要在參數實參中創建shared_ptr。
func(shared_ptr<int>(new int), g());
不同的編譯器可能有不同的調用約定,如果先new int,然后調用g(),在g()過程中發生異常,但是shared_ptr沒有創建,那么int的內存就會泄漏,正確的寫法應該是先創建智能指針。
shared_ptr<int> p(new int); f(p, g());
c.避免循環使用,循環使用可能導致內存泄漏
#include <iostream> #include <memory> using namespace std; struct A; struct B; struct A { shared_ptr<B> bptr; ~A() { cout << "A is deleted." << endl; } }; struct B { shared_ptr<A> aptr; ~B() { cout << "B is deleted." << endl; } }; int main() { shared_ptr<A> ap(new A); shared_ptr<B> bp(new B); ap->bptr = bp; bp->aptr = ap; return 0; }
這個最經典的循環引用的場景,結果是兩個指針A和B都不會刪除,存在內存泄漏。循環引用導致ap和bp的引用計數為2,離開作用域之后,ap和bp的引用計數為1,並不會減0,導致兩個指針都不會析構而產生內存泄漏。
d.通過shared_from_this()返回this指針。不要將this指針作為shared_ptr返回出來,因為this指針本質是一個裸指針,這樣可能導致重復析構。
#include <iostream> #include <memory> using namespace std; struct A { shared_ptr<A> GetSelf() { return shared_ptr<A>(this); } ~A() { cout << "A is deleted." << endl; } }; int main() { shared_ptr<A> ap(new A); shared_ptr<A> ap2 = ap->GetSelf(); return 0; } //執行結果 A is deleted. A is deleted.
這個例子中,由於同一指針(this)構造了兩個只能指針ap和ap2,而他們之間是沒有任何關系的,在離開作用域之后this將會被構造的兩個智能指針各自析構,導致重復析構的錯誤。當然,也有解決辦法,解決辦法在之后的weak_ptr介紹。
2、unique_ptr獨占智能指針
2.1 初始化
unique_ptr是一個獨占型智能指針,它不允許其他的智能指針共享其內部的指針,不允許通過賦值將一個unique_ptr賦值給另一個unique_ptr。只能通過函數來返回給其它的unique_ptr,比如move函數,但是轉移之后,不再對之前的指針具有所有權。
unique_ptr<int> uptr(new int(10)); unique_ptr<int> uptr2 = uptr; //error unique_ptr<int> uptr3 = move(uptr); //uptr將變為null
2.2 特點
2.2.1 數組
unique_ptr和shared_ptr相比除了獨占之外,unique_ptr還可以指向一個數組。
unique_ptr<int []> ptr(new int[10]); //ok ptrp[1] = 10; shared_ptr<int []> ptr2(new int[10]); //error
2.2.2 刪除器
unique_ptr必須指定刪除器類型,不像shared_ptr那樣直接指定刪除器。
shared_ptr<int> ptr(new int(1), [](int *p){delete p;}); //ok unique_ptr<int> ptr2(new int(1), [](int *p){delete p;}); //error unique_ptr<int, void(*)(int *)> ptr2(new int(1), [](int *p){delete p;}); //ok
通過指定函數類型,然后通過lambda表達式實現是可以,但是如果捕獲了變量將會編譯報錯,因為lambda表達式在沒有捕獲變量的情況下可以直接轉換為函數指針,但是捕獲了變量就無法轉換。如果要支持,可以通過std::function來解決。
unique_ptr<int, void(*)(int *)> ptr2(new int(1), [&](int *p){delete p;}); //error unique_ptr<int, std::function<void(int*)>> ptr2(new int(1), [&](int *p){delete p;}); //ok
unique_ptr支持自定義刪除器。
#include <iostream> #include <memory> #include <functional> using namespace std; struct DeleteUPtr { void operator()(int* p) { cout << "delete" << endl; delete p; } }; int main() { unique_ptr<int, DeleteUPtr> p(new int(1)); return 0; }
3、weak_ptr弱引用智能指針
弱引用智能指針weak_ptr用來監視shared_ptr,不會使引用技術加1,也不管理shared_ptr內部的指針,主要是監視shared_ptr的生命周期。weak_ptr不共享指針,不能操作資源,它的構造和析構都不會改變引用計數。
3.1 基本用法
3.1.1 觀測計數
通過use_count()方法來獲得當前資源的引用計數。
shared_ptr<int> sp(new int(10)); weak_ptr<int> wp(sp); cout << wp.use_count() << endl; //輸出1
3.1.2 觀察是否有效
shared_ptr<int> sp(new int(10)); weak_ptr<int> wp(sp); if(wp.expired()) { cout << "sp 已經釋放,無效" << endl; } else { cout << "sp 有效" << endl; }
3.1.3 監視
可以通過lock方法來獲取所監視的shared_ptr。
#include <iostream> #include <memory> using namespace std; weak_ptr<int> gw; void f() { //監聽是否釋放 if(gw.expired()) { cout << "gw is expired." << endl; } else { auto spt = gw.lock(); cout << *spt << endl; } } int main() { { auto p = make_shared<int>(20); gw = p; f(); } f(); return 0; } //執行結果 20 gw is expired.
3.2 返回this指針
sharerd_ptr不能直接返回this指針,需要通過派生std::enable_shared_from_this類,並通過其方法shared_from_this來返回智能指針,因為std::enable_shared_from_this類中有一個weak_ptr,這個weak_ptr用來觀測this指針,調用shared_from_this方法時,調用了內部的weak_ptr的lock()方法,將所觀測的sharerd_ptr返回。
#include <iostream> #include <memory> using namespace std; struct A:public enable_shared_from_this<A> { shared_ptr<A> GetSelf() { return shared_from_this(); } ~A() { cout << "A is deleted." << endl; } }; int main() { shared_ptr<A> spy(new A); shared_ptr<A> p = spy->GetSelf(); //ok return 0; } //執行結果 A is deleted.
在外面創建A對象的智能指針通過該對象返回this的智能指針是安全的,因為shared_from_this()是內部weak_ptr調用lock()方法之后返回的智能指針,在離開作用域之后,spy的引用計數為0,A對象會被析構,不會出現A對象被析構兩次的問題。
需要注意的是,獲取自身智能指針的函數僅在share_ptr<T>的構造函數調用之后才能使用,因為enable_shared_from_this內部的weak_ptr只有通過shared_ptr才能構造。
3.3 解決循環引用問題
shared_ptr的循環引用可能導致內存泄漏,之前的例子不再贅述,通過weak_ptr可以解決這個問題,怎么解決呢?答案是,將A或者B任意一個成員變量改為weak_ptr即可。
#include <iostream> #include <memory> using namespace std; struct A; struct B; struct A { shared_ptr<B> bptr; ~A() { cout << "A is deleted." << endl; } }; struct B { weak_ptr<A> aptr; ~B() { cout << "B is deleted." << endl; } }; int main() { shared_ptr<A> ap(new A); shared_ptr<B> bp(new B); ap->bptr = bp; bp->aptr = ap; return 0; } //執行結果 A is deleted. B is deleted.
這樣在對B成員賦值時,即bp->aptr = ap,由於aptr是weak_ptr,並不會增加引用計數,所以ap的計數仍然是1,在離開作用域之后,ap的引用計數會減為0,A指針會被析構,析構之后,其內部的bptr引用計數會減1,然后離開作用域之后,bp引用計數從1減為0,B對象也被析構,所以不會發生內存泄漏。