歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 智能指針 auto_ptr、scoped_ptr、shared_ptr、weak_ptr

智能指針 auto_ptr、scoped_ptr、shared_ptr、weak_ptr

日期:2017/3/1 9:12:55   编辑:Linux編程

  什麼是RAII?

RAII是Resource Acquisition Is Initialization的簡稱,是C++語言的一種管理資源、避免洩漏的慣用法。

RAII又叫做資源分配即初始化,即:定義一個類來封裝資源的分配和釋放,在構造函數完成資源的分配和初始化,在析構函數完成資源的清理,可以保證資源的正確初始化和釋放。

  為什麼要使用RAII?

在計算機系統中,資源是數量有限且對系統正常運行具有一定作用的元素。比如:網絡套接字、互斥鎖、文件句柄和內存等等,它們屬於系統資源。由於系統的資源是有限的,所以,我們在編程使用系統資源時,都必須遵循一個步驟:
  1. 申請資源;
  2. 使用資源;
  3. 釋放資源。

第一步和第三步缺一不可,因為資源必須要申請才能使用的,使用完成以後,必須要釋放,如果不釋放的話,就會造成資源洩漏。

  什麼是智能指針?

所謂智能指針就是智能/自動化的管理指針所指向的動態資源的釋放。它是一個類,有類似指針的功能。

  智能指針的實現原理

  當類中有指針成員時,一般有兩種方式來管理指針成員:
  • 一是采用值型的方式管理,每個類對象都保留一份指針指向的對象的拷貝;
  • 另一種更優雅的方式是使用智能指針,從而實現指針指向的對象的共享。
  智能指針(smart pointer)的通用實現技術是使用引用計數(reference count)。智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象的指針指向同一對象。每次創建類的新對象時,初始化指針就將引用計數置為1;當對象作為另一對象的副本而創建時,拷貝構造函數拷貝指針並增加與之相應的引用計數;對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(如果引用計數為減至0,則刪除對象),並增加右操作數所指對象的引用計數;調用析構函數時,析構函數減少引用計數(如果引用計數減至0,則刪除基礎對象)。

  常見的智能指針

包括:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost::intrusive_ptr

Boost庫的智能指針(ps:新的C++11標准中已經引入了unique_ptr/shared_ptr/weak_ptr):

  auto_ptr  獨占所有權,轉移所有權

第一種實現:最開始auto_ptr的成員變量主要有T* _ptr 和 bool _owner,主要實現原理是在構造對象時賦予其管理空間的所有權,在拷貝或賦值中轉移空間的所有權,在析構函數中當_owner為true(擁有所有權)時來釋放所有權。

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 51 52 53 54 55 56 57 template<typename T> class AutoPtr { public: //構造函數 explicit AutoPtr(T* ptr = NULL) :_ptr(ptr) , _owner(true) {} //拷貝構造 AutoPtr(AutoPtr<T>& ap) //參數不能寫成const的,這裡要修改ap對象的成員<br> :_ptr(ap._ptr) , _owner(true) { ap._owner = false; //轉讓權限 } //賦值運算符重載 AutoPtr& operator=(AutoPtr<T>& ap) { if (this! = &ap) { delete this->_ptr; this->_ptr = ap._ptr; // 轉讓權限 this->_owner = true; ap._owner = false; } return *this; } //析構函數 ~AutoPtr() { // 只刪除擁有權限的指針 if (_owner) { this->_owner = false; delete this->_ptr; } } T& operator*() { return *(this->_ptr); } T* operator->() { return this->_ptr; } T* AutoPtr<T>::GetStr() { return (this->_ptr); } protected: T* _ptr; bool _owner; //權限擁有者 };

 出現的主要問題:如果拷貝出來的對象比原來的對象先出作用域或先調用析構函數,則原來的對象的_owner雖然為false,但卻在訪問一塊已經釋放的空間,原因在於拷貝對象的釋放會導致原對象的_ptr指向的內容跟著被釋放!

問題體現如下:

ap1將析構的權限給予了ap2,由於ap1._ptr和ap2._ptr指向同一份空間,ap2在出了if作用域之後自動被釋放,進而導致ap1._ptr也被釋放。

但是在if作用域之外,又對ap1._ptr進行訪問,導致程序崩潰,這時候 ap1._ptr已經是一個野指針了,這就造成指針的懸掛的問題!

1 2 3 4 5 6 7 8 9 10 11 void Test() { AutoPtr<int> ap1(new int(1)); if (true) { AutoPtr<int> ap2(ap1); } //這裡的ap1._ptr已經是一個野指針了,這就造成指針的懸掛的問題 *(ap1.GetStr() )= 10; }

  auto_ptr的第二種實現方法:還是管理空間的所有權轉移,但這種實現方法中沒有_owner權限擁有者。構造和析構和上述實現方法類似,但拷貝和賦值後直接將_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 51 52 template<typename T> class AutoPtr { public: //構造函數 explicit AutoPtr(T* ptr = NULL) //不能寫成const T* ptr,否則出現const類型賦值給非const類型的問題 :_ptr(ptr) {} //拷貝構造 AutoPtr(AutoPtr<T>& ap) :_ptr(ap._ptr) { ap._ptr=NULL; } //賦值運算符重載 AutoPtr& operator=(AutoPtr<T>& ap) { if (this! = &ap) { delete this->_ptr; this->_ptr = ap._ptr; ap._ptr = NULL; } return *this; } //析構函數 ~AutoPtr() { // 只刪除擁有權限的指針 if (_ptr) { delete _ptr; } } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } T* GetStr() { return _ptr; } protected: T* _ptr; };

  這種實現方式很好的解決了舊版本野指針問題,但是由於它實現了完全的權限轉移,所以導致在拷貝構造和賦值之後只有一個指針可以使用,而其他指針都置為NULL,使用很不方便,而且還很容易對NULL指針進行解引用,導致程序崩潰,其危害也是比較大的。

  scoped_ptr  獨占所有權,防拷貝

scoped_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 template<typename T> class ScopedPtr { public: ScopedPtr(T* ptr = NULL) :_ptr(ptr) {} T& operator*() { return *_ptr; } T* operator->() { return _ptr; } T* GetStr() { return _ptr; } //析構函數 ~ScopedPtr() { if (_ptr!=NULL) { delete _ptr; } } protected: //防拷貝 ScopedPtr(ScopedPtr<T>& ap); ScopedPtr& operator=(ScopedPtr<T>& ap); T* _ptr; };

scoped_ptr的實現和auto_ptr非常類似,不同的是 scoped_ptr有著更嚴格的使用限制——不能拷貝,這就意味著scoped_ptr 是不能轉換其所有權的。

在一般的情況下,如果不需要對於指針的內容進行拷貝,賦值操作,而只是為了防止內存洩漏的發生,scoped_ptr完全可以勝任。

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2016-08/134315p2.htm

Copyright © Linux教程網 All Rights Reserved