c++ ---智能指針

爲什麼要使用智能指針?
智能指針的作用就是管理一個指針,因爲存在以下的情況:申請的空間在函數結束的時候忘記釋放,造成內存泄漏。使用智能指針可以很大程度上避免這一問題,因爲智能指針就是一個類,當超出了類的作用域之後,類就會自動調用析構函數,析構函數就會自動釋放資源。所以析構函數的作用就是在函數結束時自動釋放空間,不需要手動釋放內存空間。

一.RAII

  • (1)RAII,也稱爲“資源獲取就是初始化”,是c++等編程語言常用的管理資源、避免內存泄露的方法。
  • (2)在對象構造的時候獲取資源,接着控制對資源的訪問使之在對象的聲明週期內始終保持有效,最後在對象析構的時候釋放資源。
  • (3)我們實際上把管理一份資源的責任交給了一個對象,這樣做有兩大好處。
    不需要顯示的釋放資源
    採用這種方式,對象所需的資源在生命週期內始終保持有效。

二.auto_ptr

auto_ptr的實現原理:管理權轉移的意思。但是存在內存崩潰的問題。

#include<iostream>
using namespace std;

template<class T>
class AutoPtr
{
public:
	AutoPtr(T* ptr = NULL)
		:_ptr(ptr)
	{}

	~AutoPtr()
	{
		if (_ptr)
			delete _ptr;
	}

	//一旦發生了拷貝,就將p中的資源轉移到當前對象中,
	//然後讓p與其所管理的對象斷開聯繫
	//這樣就解決了一塊空間被多個對象使用而造成內存崩潰問題。
	AutoPtr(AutoPtr<T>& p)
		:_ptr(p._ptr)
	{
		p._ptr = nullptr;
	}

	AutoPtr<T>& operator=(AutoPtr<T>& p)
	{
		if (this != &p)
		{
			// 釋放當前對象中資源 
			if (_ptr)
				delete _ptr;

			// 轉移ap中資源到當前對象中 
			_ptr = p._ptr;
			p._ptr = nullptr;
		}
		return *this;
	}

	T& operator*(){ return *_ptr; }
	T* operator->(){ return _ptr; }
private:
	T* _ptr;
};

int main()
{
	AutoPtr<int> p(new int (10));

	//這裏拷貝之後把p的對象指針賦空了,到中p對象懸空
	//通過p對象進行資源訪問就會出現問題。
	AutoPtr<int> copy(p);
	
	copy = p;
	system("pause");
	return 0;
}

三.unique_ptr

unique_ptr實現獨佔式擁有或嚴格擁有的概念,保證同一時間內只有一個智能指針可以指向該對象。

unique_ptr<string> p1(new string ("auto"));
unique_ptr <string> p4;
p4 = p3;
//此時會報錯
//編譯器認爲p4 = p3非法,避免了p3不再指向有效數據的問題。因此unique_ptr比auto_ptr更安全

當程序試圖將一個unique_ptr賦值給另一個的時候,如果源unique_ptr是一個臨時右值,編譯器允許這麼做;如果源unique_ptr將存在一段時間,編譯器將禁止這麼做。

unique_ptr<string> p1(new string ("hello world"));
unique_ptr<string> p2;
p2 = p1;                                          // #1 not allowed

unique_ptr<string> p;
p3 = unique_ptr<string>(new string ("You"));           // #2 allowed

其中#1留下懸掛的unique_ptr(pu1),這可能導致危害。而#2不會留下懸掛的unique_ptr,因爲它調用 unique_ptr 的構造函數,該構造函數創建的臨時對象在其所有權讓給 pu3 後就會被銷燬。這種隨情況而已的行爲表明,unique_ptr 優於允許兩種賦值的auto_ptr 。

注:如果確實想執行類似與#1的操作,要安全的重用這種指針,可給它賦新值。C++有一個標準庫函數std::move(),讓你能夠將一個unique_ptr賦給另一個。例如:

unique_ptr<string> p1, p2;
p1 = demo("hello");
p2 = move(ps1);
p1 = demo("alexia");
cout << *p2 << *p1 << endl;

四.shared_ptr

shared_ptr實現共享式擁有概念。多個智能指針可以指向相同對象,該對象和其相關資源會在“最後一個引用被銷燬”時候釋放。從名字share就可以看出了資源可以被多個指針共享,它使用計數機制來表明資源被幾個指針共享。可以通過成員函數use_count()來查看資源的所有者個數。除了可以通過new來構造,還可以通過傳入auto_ptr, unique_ptr,weak_ptr來構造。當我們調用release()時,當前指針會釋放資源所有權,計數減一。當計數等於0時,資源會被釋放。

shared_ptr 是爲了解決 auto_ptr 在對象所有權上的侷限性(auto_ptr 是獨佔的), 在使用引用計數的機制上提供了可以共享所有權的智能指針。

成員函數:

use_count 返回引用計數的個數

unique 返回是否是獨佔所有權( use_count 爲 1)

swap 交換兩個 shared_ptr 對象(即交換所擁有的對象)

reset 放棄內部對象的所有權或擁有對象的變更, 會引起原有對象的引用計數的減少

get 返回內部對象(指針), 由於已經重載了()方法, 因此和直接使用對象是一樣的.如 shared_ptr sp(new int(1)); sp 與 sp.get()是等價的

1.shared_ptr的線程安全問題

  • (1)智能指針對象中引用計數是多個智能指針對象共享的,兩個線程中智能指針的引用計數同時++或者–,這個操作不是原子的,引用計數原來是1,++了兩次,可能還是2.這樣引用計數就錯亂了。會導致資源未釋放或者程序崩潰的問題。所以只能在指針中引用計數++,–是需要上鎖的,也就是說引用計數的操作是線程安全的。
  • (2)智能指針管理的對象存放在棧上,兩個線程同時去訪問,會導致線程安全問題。

2.shared_ptr的循環引用

struct ListNode 
{    
	int _data;    
	shared_ptr<ListNode> _prev;    
	shared_ptr<ListNode> _next;
 
	~ListNode()
	{ 
		cout << "~ListNode()" << endl; 
	} 
};
 
int main()
{
 	shared_ptr<ListNode> node1(new ListNode);    
 	shared_ptr<ListNode> node2(new ListNode);    
 	cout << node1.use_count() << endl;    
 	cout << node2.use_count() << endl;
 
    node1->_next = node2;    
    node2->_prev = node1;
 
    cout << node1.use_count() << endl;    
    cout << node2.use_count() << endl;
 
    return 0; 
}

  • (1)node1和node2兩個智能指針對象指向兩個節點,引用計數變成1,我們不需要手動delete。
  • (2)node1的_next指向node2,node2的_prev指向node1,引用計數變成2。
  • (3)node1和node2析構,引用計數減到1,但是_next還指向下一個節點。但是_prev還指向上一個節點。 * * (4)也就是說_next析構了,node2就釋放了。
  • (5)也就是說_prev析構了,node1就釋放了。
  • (6)但是_next屬於node的成員,node1釋放了,_next纔會析構,而node1由_prev管理,_prev屬於node2 成員,所以這就叫循環引用,誰也不會釋放。
//解決方案:在引用計數的場景下,把節點中的_prev和_next改成weak_ptr就可以了 
// 原理就是,node1->_next = node2;和node2->_prev = node1;時weak_ptr的_next和_prev不會增加 node1和node2的引用計數。 
struct ListNode 
{    
	int _data;    
	weak_ptr<ListNode> _prev;    
	weak_ptr<ListNode> _next; 
  
	~ListNode()
	{ 
		cout << "~ListNode()" << endl; 
	} 
};
 
int main() 
{    
	shared_ptr<ListNode> node1(new ListNode);    
	shared_ptr<ListNode> node2(new ListNode);    
	cout << node1.use_count() << endl;    
	cout << node2.use_count() << endl;
 
    node1->_next = node2;    
    node2->_prev = node1;
 
    cout << node1.use_count() << endl;    
    cout << node2.use_count() << endl;
 
    return 0; 
}

五.weak_ptr

weak_ptr 是一種不控制對象生命週期的智能指針, 它指向一個 shared_ptr 管理的對象. 進行該對象的內存管理的是那個強引用的 shared_ptr. weak_ptr只是提供了對管理對象的一個訪問手段。weak_ptr 設計的目的是爲配合 shared_ptr 而引入的一種智能指針來協助 shared_ptr 工作, 它只可以從一個 shared_ptr 或另一個 weak_ptr 對象構造, 它的構造和析構不會引起引用記數的增加或減少。weak_ptr是用來解決shared_ptr相互引用時的死鎖問題,如果說兩個shared_ptr相互引用,那麼這兩個指針的引用計數永遠不可能下降爲0,資源永遠不會釋放。它是對對象的一種弱引用,不會增加對象的引用計數,和shared_ptr之間可以相互轉化,shared_ptr可以直接賦值給它,它可以通過調用lock函數來獲得shared_ptr。

注意的是我們不能通過weak_ptr直接訪問對象的方法,比如B對象中有一個方法print(),我們不能這樣訪問,pa->pb_->print(); 英文pb_是一個weak_ptr,應該先把它轉化爲shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章