閱讀938 返回首頁    go 阿裏雲 go 技術社區[雲棲]


c++11之智能指針

使用c++,除了c++的語法外,指針是我們麵臨的最的大一個問題,由於使用不當就會導致程序意外退出,或著內存的占用越來越多,總結起來這些錯誤由以下三個原因造成。

       1 野指針:指針指向的內存已經被釋放,但是我們還在使用該指針,或者還在使用之前指向的指針,此時程序會崩潰,也有可能導致已經釋放的內存被重新分配給程序使用,造成意想不到的後果。

       2 重複釋放:程序嚐試釋放已經被釋放的內存單元,或者釋放已經被重新分配過的內存單元,會導致重複釋放錯誤。

       3 內存泄漏:不需要的內存單元沒有釋放,一旦程序一直在重複這樣的操作,會導致程序的內存占用月來越高。

      雖然顯示的手動管理內存,給程序的內存管理帶來了很大的自由度,可以高效的利用係統的內存,但是也是非常容易出錯的,隨著多線程程序的出現和廣泛使用,這樣的問題會更加嚴重,因此c++11通過智能指針來擺脫顯示的內存管理,標準庫還實現了‘最小垃圾回收’的支持。

如果你想學習C/C++可以來這個群,首先是三三零,中間是八五九,最後是七六六,裏麵有大量的學習資料可以下載。

      c++11中通過unique_ptr,shared_ptr和weak_ptr等智能指針來實現自動釋放堆內存。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50

#include <memory>
#include <iostream>

using namespace std;

struct Test{
int a;
};
class Demo {
public:
Demo() {
b = new Test();
std::cout << "Demo ()" << std::endl;
}
~Demo() {
std::cout << "~Demo ()" << std::endl;
}
void Say() {
std::cout << "Dem::Say()==>" << b->a << std::endl;
}
public:
Test *b;
};

int main()
{
std::cout << "////////////////unique_ptr///////////////////////" << std::endl;
////////////////unique_ptr///////////////////////
unique_ptr<Demo> unique_ptr_demo1(new Demo);
unique_ptr_demo1->Say();
// unique_ptr<Demo> unique_ptr_demo2 = unique_ptr_demo1; //無法通過編譯
Demo demo2 = *unique_ptr_demo1;

unique_ptr<Demo> unique_ptr_demo3 = move(unique_ptr_demo1);
unique_ptr_demo3->Say();
// Demo demo5 = *unique_ptr_demo1; //運行時錯誤
//
////////////////shared_ptr///////////////////////
std::cout << "////////////////shared_ptr///////////////////////" << std::endl;
shared_ptr<Demo> shared_ptr_demo1(new Demo());
shared_ptr_demo1->Say();
// shared_ptr_demo1.reset();
shared_ptr<Demo> shared_ptr_demo2 = shared_ptr_demo1;
shared_ptr_demo1.reset();
// shared_ptr_demo2.reset(); //運行時錯誤2
shared_ptr_demo2->Say();
return 0;
}


        這裏我們可以看到不用我們手動釋放指針指向的內存,編譯器會幫我們釋放該內存,對於析構函數的三次調用,分別對應unique_ptr_demo3,demo2和shared_ptr_demo1.

我們主要看unique_ptr和shared_ptr的區別,unique_ptr跟所指對象的內存緊緊的綁定在一起,不和其他的指針共享其指向的內存(例子中:無法通過編譯的注釋),也就是說unique_ptr不能賦值給別的unique_ptr,更深的講,實際上unique_ptr的拷貝構造函數被刪除,所以我們不能賦值。但是我們可以通過move語義來竊取unique_ptr的內存,但是要注意注釋:運行時錯誤,我們竊取了內存後,該指針就不能在使用了,因為他指向的內存已經被偷走了,特別是當它的成員變量中有指針時,會造成程序崩潰。

       shared_ptr可以賦值,允許多個shared_ptr共享一塊內存,它采用了引用計數的內存管理方式,因此當它放棄了所有權時並不會釋放內存,不回影響其他引用這塊內存的指針,隻有當引用計數變為0時才會釋放內存。我們可以看到例子中 shared_ptr_demo1.reset();並沒有影響shared_ptr_demo2(注:shared_ptr_demo1.reset的注釋此處會徹底釋放內存,shared_ptr_demo2會成為一個空指針)所以當我們通過reset顯示的來銷毀指針時,當引用計數沒有為0時隻是把指針設置成nullptr而已。因此既然編輯器會幫我們管理內存,如果不是特殊需求,就不要去顯示的調用reset函數來釋放銷毀指針。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40

#include <memory>
#include <iostream>

using namespace std;

class Demo{
public:
void Say() {
std::cout << "Demo" << std::endl;
}
};

template <class T>
void CheckPtrIsValid(weak_ptr<T>& temp) {
shared_ptr<T> demo = temp.lock();
if (demo != nullptr) {
std::cout << "ptr is good" << std::endl;
}else{
std::cout << "ptr is null" << std::endl;
}
}

int main()
{
shared_ptr<Demo> shared_ptr_demo1(new Demo());
shared_ptr<Demo> shared_ptr_demo2 = shared_ptr_demo1;
weak_ptr<Demo> weak_ptr_demo1 = shared_ptr_demo1;
CheckPtrIsValid(weak_ptr_demo1);

shared_ptr_demo1->Say();
shared_ptr_demo2->Say();
shared_ptr_demo1.reset();
CheckPtrIsValid(weak_ptr_demo1);

shared_ptr_demo2.reset();
CheckPtrIsValid(weak_ptr_demo1);
return 0;
}


     最後說道weak_ptr,它的作用不同於以上兩種,官方說法是它可以指向shared_ptr指向的內存,但不擁有該內存,當調用lock時可以返回指向內存的shared_ptr,上麵的例子中有一個檢查指針是否有效的函數,我們可以看到當我們兩次調用reset釋放指針使得內存引用計數變為0,釋放內存。這裏是2次,而不是三次就非常明確的證明了,weak_ptr並沒有增加引用計數,也就是說並沒有擁有該內存。

說到這裏,我們也應該感覺到我們使用最多的應該是shared_ptr了,隻有在我們需要一個指針獨占一塊內存是才會用到unique _ptr,而weak_ptr在必要的時候可以用來監測shared_ptr指向的內存是否有效,這是一個非常重要且有意義的用法。

最後更新:2017-04-17 16:00:45

  上一篇:go 開源大數據周刊-第48期
  下一篇:go 阿裏雲,我的一些看法