1、智能指針簡介c++
a smart pointer is an abstract data type that simulates a pointer while providing added features, such as automatic memory management or bounds checking.程序員
智能指針和普通指針的區別在於智能指針其實是對普通指針加了一層封裝機制,這樣的一層封裝機制的目的是爲了使得智能指針能夠方便的管理一個對象的生命期。安全
在C++中,咱們知道,若是使用普通指針來建立一個指向某個對象的指針,那麼在使用完這個對象以後咱們須要本身刪除它,例如:多線程
ObjectType* temp_ptr = new ObjectType();
temp_ptr->foo();
delete temp_ptr;app
不少材料上都會指出說若是程序員忘記在調用完temp_ptr以後刪除temp_ptr,那麼會形成一個懸掛指針(dangling pointer),也就是說這個指針如今指向的內存區域其內容程序員沒法把握和控制,也可能很是容易形成內存泄漏。ide
智能指針設計思想:將基本類型指針封裝爲類對象指針(這個類確定是個模板,以適應不一樣基本類型的需求),並在析構函數裏編寫delete語句刪除指針指向的內存空間。函數
auto_ptr
has semantics of strict ownership, meaning that the
auto_ptr
instance is the sole entity responsible for the object's lifetime.
If an auto_ptr
is copied, the source loses the reference.
1 int main(int argc, char **argv) 2 { 3 int *i = new int; 4 auto_ptr<int> x(i); 5 auto_ptr<int> y; 6 7 y = x; 8 9 cout << x.get() << endl; // Print NULL 10 cout << y.get() << endl; // Print non-NULL address i 11 12 return 0; 13 }
x通過assignment copy之後,全部權發生轉移,資源的全部權轉移到y,x已經變成nullptr。這時再對x進行操做,極可能發生core。this
auto_ptr採用能夠採用copy語義來轉移指針資源的全部權的同時將原指針置爲NULL,這跟一般理解的copy行爲是不一致的,而這樣的行爲要有些場合下不是咱們但願看到的.spa
auto_ptr兩個缺陷:線程
o 複製和賦值會改變資源的全部權,不符合人的直覺。
o 在 STL 容器中沒法使用auto_ptr ,由於容器內的元素必需支持可複製(copy constructable)和可賦值(assignable)。
二、unique_ptr
1 auto_ptr<string> p1(new string ("auto") ; //#1 2 auto_ptr<string> p2; //#2 3 p2 = p1; //#3
在語句#3中,p2接管string對象的全部權後,p1的全部權將被剝奪。前面說過,這是好事,可防止p1和p2的析構函數試圖刪同—個對象;
但若是程序隨後試圖使用p1,這將是件壞事,由於p1再也不指向有效的數據。
下面來看使用unique_ptr的狀況:
1 unique_ptr<string> p3 (new string ("auto"); //#4 2 unique_ptr<string> p4; //#5 3 p4 = p3; //#6
編譯器認爲語句#6非法,避免了p3再也不指向有效數據的問題。所以,unique_ptr比auto_ptr更安全。
本質上來講,就是unique_ptr禁用了copy,而用move替代。unique_ptr禁止左值複製,不過能夠經過std::move將左值轉換爲右值屬性
1 unique_ptr<string> ps1, ps2; 2 ps1 = demo("hello"); 3 ps2 = move(ps1); 4 ps1 = demo("alexia"); 5 cout << *ps2 << *ps1 << endl;
三、shared_ptr
unique_ptr是獨佔資源的,shared_ptr能夠實現共享資源。
When using unique_ptr
, there can be at most one unique_ptr
pointing at any one resource. When that unique_ptr
is destroyed, the resource is automatically reclaimed. Because there can only be one unique_ptr
to any resource, any attempt to make a copy of a unique_ptr
will cause a compile-time error. For example, this code is illegal:
1 unique_ptr<T> myPtr(new T); // Okay 2 unique_ptr<T> myOtherPtr = myPtr; // Error: Can't copy unique_ptr
However, unique_ptr
can be moved using the new move semantics:
unique_ptr<T> myPtr(new T); // Okay unique_ptr<T> myOtherPtr = std::move(myPtr); // Okay, resource now stored in myOtherPtr
Similarly, you can do something like this:
1 unique_ptr<T> MyFunction() { 2 unique_ptr<T> myPtr(/* ... */); 3 4 /* ... */ 5 6 return myPtr; 7 }
This idiom means "I'm returning a managed resource to you. If you don't explicitly capture the return value, then the resource will be cleaned up. If you do, then you now have exclusive ownership of that resource." In this way, you can think of unique_ptr
as a safer, better replacement for auto_ptr
.
shared_ptr
, on the other hand, allows for multiple pointers to point at a given resource. When the very last shared_ptr
to a resource is destroyed, the resource will be deallocated. For example, this code is perfectly legal:
1 shared_ptr<T> myPtr(new T); // Okay 2 shared_ptr<T> myOtherPtr = myPtr; // Sure! Now have two pointers to the resource.
Internally, shared_ptr
uses reference counting to track how many pointers refer to a resource, so you need to be careful not to introduce any reference cycles.
簡單來講,shared_ptr經過引用計數refCount保證有多個ptr共同擁有資源的全部權,當全部的資源都釋放全部權refCount=0,釋放該資源。
shared_ptr的簡單實現:
1 template <typename V> 2 class SmartPtr { 3 private: 4 int * refcnt; 5 V * v; 6 public: 7 SmartPtr(V* ptr): v(ptr) { 8 refcnt = new int(1); 9 } 10 11 SmartPtr(const SmartPtr& ptr) { 12 this->v = ptr.v; 13 this->refcnt = ptr.refcnt; 14 *refcnt += 1; 15 } 16 17 ~SmartPtr() { 18 cout << "to delete a smart pointer" << endl; 19 *refcnt -= 1; 20 21 if (*refcnt == 0) { 22 delete v; 23 delete refcnt; 24 } 25 } 26 }; 27 28 int main() { 29 A * ptrA = new A(); 30 SmartPtr<A> sp1(ptrA); 31 SmartPtr<A> sp2 = sp1; 32 33 return 0; 34 }
這個例子中中須要注意的點是引用計數是全部管理同一個指針的智能指針所共享的,因此在這個例子中,sp1和sp2的引用計數指向的是相同的一個整數。
咱們看一下這個例子的輸出:
# g++ -o smart myShare.cpp
# ./smart
create object of A
to delete a smart pointer
to delete a smart pointer
destroy an object A
能夠看到,這個和shared_ptr同樣能夠正確地delete指針。
四、weak_ptr
weak_ptr和Raw Pointer很相似,只標記指向該資源,沒有該資源的全部權。同時weak_ptr擴展了Raw Pointer的功能。
std::weak_ptrs are typically created from std::shared_ptrs. They point to the same place as the std::shared_ptrs initializ‐
ing them, but they don’t affect the reference count of the object they point to:
auto spw = // after spw is constructed, std::make_shared<Widget>(); // the pointed-to Widget's // ref count (RC) is 1. (See // Item 21 for info on // std::make_shared.) … std::weak_ptr<Widget> wpw(spw); // wpw points to same Widget // as spw. RC remains 1 … spw = nullptr; // RC goes to 0, and the // Widget is destroyed. // wpw now dangles
std::weak_ptrs that dangle are said to have expired. You can test for this directly,
if (wpw.expired()) … // if wpw doesn't point // to an object…
有兩種方法從weak_ptr獲取shared_ptr:
One form is std::weak_ptr::lock, which returns a std::shared_ptr. The std::shared_ptr is null
if the std::weak_ptr has expired:
std::shared_ptr<Widget> spw1 = wpw.lock(); // if wpw's expired, // spw1 is null auto spw2 = wpw.lock(); // same as above, // but uses auto
The other form is the std::shared_ptr constructor taking a std::weak_ptr as an
argument. In this case, if the std::weak_ptr has expired, an exception is thrown:
std::shared_ptr<Widget> spw3(wpw); // if wpw's expired, // throw std::bad_weak_ptr
As a final example of std::weak_ptr’s utility, consider a data structure with objects
A, B, and C in it, where A and C share ownership of B and therefore hold
std::shared_ptrs to it:
There are three choices:
• A raw pointer. With this approach, if A is destroyed, but C continues to point to
B, B will contain a pointer to A that will dangle. B won’t be able to detect that, so B
may inadvertently dereference the dangling pointer. That would yield undefined
behavior.
• A std::shared_ptr. In this design, A and B contain std::shared_ptrs to each
other. The resulting std::shared_ptr cycle (A points to B and B points to A) will
prevent both A and B from being destroyed. Even if A and B are unreachable from
other program data structures (e.g., because C no longer points to B), each will
have a reference count of one. If that happens, A and B will have been leaked, for
all practical purposes: it will be impossible for the program to access them, yet
their resources will never be reclaimed.
• A std::weak_ptr. This avoids both problems above. If A is destroyed, B’s
pointer back to it will dangle, but B will be able to detect that. Furthermore,
though A and B will point to one another, B’s pointer won’t affect A’s reference
count, hence can’t keep A from being destroyed when std::shared_ptrs no
longer point to it.
在選擇具體指針類型的時候,經過問如下幾個問題就能知道使用哪一種指針了。
若是指針變量須要綁定資源的全部權,那麼會選擇unique_ptr或shared_ptr。它們能夠經過RAII完成對資源生命期的自動管理。若是不須要擁有資源的全部權,那麼會選擇weak_ptr和raw pointer,這兩種指針變量在離開做用域時不會對其所指向的資源產生任何影響。
獨佔則使用unique_ptr(人無我有,人有我丟),不然使用shared_ptr(你有我有全都有)。這一點很好理解。
1 auto p = make_shared<int>(1); 2 auto result = f(p.get());
這樣會衍生出另一個問題,爲什麼unique_ptr不能和weak_ptr配合?這是由於unique_ptr是獨佔全部權,也就是說資源的生命期等於指針變量的生命期,那麼程序員能夠很容易經過指針變量的生命期來判斷資源是否有效,這樣weak_ptr就再也不有必要了。而相對來講,shared_ptr則很差判斷,特別是多線程環境下。
另外,不少人說weak_ptr的做用是能夠破除循環引用,這個說法是對的,但沒有抓住本質(祼指針也能夠破除,那爲什麼要用weak_ptr?)。寫出循環引用的緣由是由於程序員本身沒有理清楚資源的全部權問題。