【c++工程實踐】智能指針

1、智能指針簡介c++

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語句刪除指針指向的內存空間。函數

STL一共給咱們提供了四種智能指針:auto_ptr、unique_ptr、shared_ptr和weak_ptr(本文章暫不討論)。 模板auto_ptr是C++98提供的解決方案,C+11已將將其摒棄,並提供了另外兩種解決方案。
 
2、四種智能指針簡介
1. auto_ptr
智能指針中很重要的一個概念是全部權問題:智能指針和祼指針都統稱爲指針,它們共同的目標是經過地址去表明資源,智能指針是否有該資源的全部權,是取決於使用各類智能指針的關鍵。
The  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

unique_ptr是c++11中用於替代auto_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.

( 符號 &(reference),表示".....的地址"("address of"),所以成爲地址操做符(adress operator),又稱引用操做符(reference operator)
符號 *(dereference),表示".....所指向的值"("value pointed to by"))
 
3、智能指針總結

在選擇具體指針類型的時候,經過問如下幾個問題就能知道使用哪一種指針了。

  • 指針是否須要擁有資源的全部權?

若是指針變量須要綁定資源的全部權,那麼會選擇unique_ptr或shared_ptr。它們能夠經過RAII完成對資源生命期的自動管理。若是不須要擁有資源的全部權,那麼會選擇weak_ptr和raw pointer,這兩種指針變量在離開做用域時不會對其所指向的資源產生任何影響。

  • 若是指針擁有資源的全部權(owning pointer),那麼該指針是否須要獨佔全部權?

獨佔則使用unique_ptr(人無我有,人有我丟),不然使用shared_ptr(你有我有全都有)。這一點很好理解。

  • 若是不擁有資源的全部權(non-owning pointer),那麼指針變量是否須要在適當的時候感知到資源的有效性?
若是須要則使用weak_ptr,它能夠在適當的時候經過weak_ptr::lock()得到全部權,當擁有全部權後即可以得知資源的有效性。如不須要,則使用祼指針。這一般是程序員知道在祼指針的做用域內它是有效的,而且使用祼指針效率更高,例如
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?)。寫出循環引用的緣由是由於程序員本身沒有理清楚資源的全部權問題。
相關文章
相關標籤/搜索